Commit 7647353f authored by syasonik's avatar syasonik

Move MetricsDashboard to Metrics::Dashboard

parent 2356e77e
......@@ -160,7 +160,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
def metrics_dashboard
return render_403 unless Feature.enabled?(:environment_metrics_use_prometheus_endpoint, @project)
result = Gitlab::MetricsDashboard::Service.new(@project, @current_user, environment: environment).get_dashboard
result = Gitlab::Metrics::Dashboard::Service.new(@project, @current_user, environment: environment).get_dashboard
respond_to do |format|
if result[:status] == :success
......
# frozen_string_literal: true
module EE
module Gitlab
module Metrics
module Dashboard
module Processor
extend ::Gitlab::Utils::Override
EE_SEQUENCE = [
Stages::AlertsInserter
].freeze
override :sequence
def sequence
super + EE_SEQUENCE
end
end
end
end
end
end
# frozen_string_literal: true
require 'set'
module EE
module Gitlab
module Metrics
module Dashboard
module Stages
class AlertsInserter < ::Gitlab::Metrics::Dashboard::Stages::BaseStage
def transform!(dashboard)
return if metrics_with_alerts.empty?
for_metrics(dashboard) do |metric|
next unless metrics_with_alerts.include?(metric[:metric_id])
metric[:alert_path] = alert_path(metric[:metric_id], project, environment)
end
end
private
def metrics_with_alerts
return @metrics_with_alerts if @metrics_with_alerts
alerts = ::Projects::Prometheus::AlertsFinder
.new(project: project, environment: environment)
.execute
@metrics_with_alerts = Set.new(alerts.map(&:prometheus_metric_id))
end
def alert_path(metric_id, project, environment)
::Gitlab::Routing.url_helpers.project_prometheus_alert_path(project, metric_id, environment_id: environment.id, format: :json)
end
end
end
end
end
end
end
# frozen_string_literal: true
module EE
module Gitlab
module MetricsDashboard
module Processor
extend ::Gitlab::Utils::Override
EE_SEQUENCE = [
Stages::AlertsInserter
].freeze
override :sequence
def sequence
super + EE_SEQUENCE
end
end
end
end
end
# frozen_string_literal: true
require 'set'
module EE
module Gitlab
module MetricsDashboard
module Stages
class AlertsInserter < ::Gitlab::MetricsDashboard::Stages::BaseStage
def transform!(dashboard)
return if metrics_with_alerts.empty?
for_metrics(dashboard) do |metric|
next unless metrics_with_alerts.include?(metric[:metric_id])
metric[:alert_path] = alert_path(metric[:metric_id], project, environment)
end
end
private
def metrics_with_alerts
return @metrics_with_alerts if @metrics_with_alerts
alerts = ::Projects::Prometheus::AlertsFinder
.new(project: project, environment: environment)
.execute
@metrics_with_alerts = Set.new(alerts.map(&:prometheus_metric_id))
end
def alert_path(metric_id, project, environment)
::Gitlab::Routing.url_helpers.project_prometheus_alert_path(project, metric_id, environment_id: environment.id, format: :json)
end
end
end
end
end
end
......@@ -2,10 +2,10 @@
require 'spec_helper'
describe Gitlab::MetricsDashboard::Processor do
describe Gitlab::Metrics::Dashboard::Processor do
let(:project) { build(:project) }
let(:environment) { create(:environment, project: project) }
let(:dashboard_yml) { YAML.load_file('spec/fixtures/lib/gitlab/metrics_dashboard/sample_dashboard.yml') }
let(:dashboard_yml) { YAML.load_file('spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml') }
describe 'sequence' do
let(:environment) { build(:environment) }
......
# frozen_string_literal: true
module Gitlab
module Metrics
module Dashboard
# Responsible for processesing a dashboard hash, inserting
# relevant DB records & sorting for proper rendering in
# the UI. These includes shared metric info, custom metrics
# info, and alerts (only in EE).
class Processor
SEQUENCE = [
Stages::CommonMetricsInserter,
Stages::ProjectMetricsInserter,
Stages::Sorter
].freeze
def initialize(project, environment)
@project = project
@environment = environment
end
# Returns a new dashboard hash with the results of
# running transforms on the dashboard.
def process(dashboard)
dashboard = dashboard.deep_transform_keys(&:to_sym)
stage_params = [@project, @environment]
sequence.each { |stage| stage.new(*stage_params).transform!(dashboard) }
dashboard
end
private
def sequence
SEQUENCE
end
end
end
end
end
Gitlab::Metrics::Dashboard::Processor.prepend EE::Gitlab::Metrics::Dashboard::Processor
# frozen_string_literal: true
# Fetches the metrics dashboard layout and supplemented the output with DB info.
module Gitlab
module Metrics
module Dashboard
class Service < ::BaseService
SYSTEM_DASHBOARD_NAME = 'common_metrics'
SYSTEM_DASHBOARD_PATH = Rails.root.join('config', 'prometheus', "#{SYSTEM_DASHBOARD_NAME}.yml")
# Returns a DB-supplemented json representation of a dashboard config file.
def get_dashboard
dashboard_string = Rails.cache.fetch(cache_key) { system_dashboard }
dashboard = process_dashboard(dashboard_string)
success(dashboard: dashboard)
rescue Gitlab::Metrics::Dashboard::Stages::BaseStage::DashboardLayoutError => e
error(e.message, :unprocessable_entity)
end
private
# Returns the base metrics shipped with every GitLab service.
def system_dashboard
YAML.load_file(SYSTEM_DASHBOARD_PATH)
end
def cache_key
"metrics_dashboard_#{SYSTEM_DASHBOARD_NAME}"
end
# Returns a new dashboard Hash, supplemented with DB info
def process_dashboard(dashboard)
Processor.new(project, params[:environment]).process(dashboard)
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Metrics
module Dashboard
module Stages
class BaseStage
DashboardLayoutError = Class.new(StandardError)
DEFAULT_PANEL_TYPE = 'area-chart'
attr_reader :project, :environment
def initialize(project, environment)
@project = project
@environment = environment
end
# Entry-point to the stage
# @param dashboard [Hash]
# @param project [Project]
# @param environment [Environment]
def transform!(_dashboard)
raise NotImplementedError
end
protected
def missing_panel_groups!
raise DashboardLayoutError.new('Top-level key :panel_groups must be an array')
end
def missing_panels!
raise DashboardLayoutError.new('Each "panel_group" must define an array :panels')
end
def missing_metrics!
raise DashboardLayoutError.new('Each "panel" must define an array :metrics')
end
def for_metrics(dashboard)
missing_panel_groups! unless dashboard[:panel_groups].is_a?(Array)
dashboard[:panel_groups].each do |panel_group|
missing_panels! unless panel_group[:panels].is_a?(Array)
panel_group[:panels].each do |panel|
missing_metrics! unless panel[:metrics].is_a?(Array)
panel[:metrics].each do |metric|
yield metric
end
end
end
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Metrics
module Dashboard
module Stages
class CommonMetricsInserter < BaseStage
# For each metric in the dashboard config, attempts to
# find a corresponding database record. If found,
# includes the record's id in the dashboard config.
def transform!(dashboard)
common_metrics = ::PrometheusMetric.common
for_metrics(dashboard) do |metric|
metric_record = common_metrics.find { |m| m.identifier == metric[:id] }
metric[:metric_id] = metric_record.id if metric_record
end
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Metrics
module Dashboard
module Stages
class ProjectMetricsInserter < BaseStage
# Inserts project-specific metrics into the dashboard
# config. If there are no project-specific metrics,
# this will have no effect.
def transform!(dashboard)
project.prometheus_metrics.each do |project_metric|
group = find_or_create_panel_group(dashboard[:panel_groups], project_metric)
panel = find_or_create_panel(group[:panels], project_metric)
find_or_create_metric(panel[:metrics], project_metric)
end
end
private
# Looks for a panel_group corresponding to the
# provided metric object. If unavailable, inserts one.
# @param panel_groups [Array<Hash>]
# @param metric [PrometheusMetric]
def find_or_create_panel_group(panel_groups, metric)
panel_group = find_panel_group(panel_groups, metric)
return panel_group if panel_group
panel_group = new_panel_group(metric)
panel_groups << panel_group
panel_group
end
# Looks for a panel corresponding to the provided
# metric object. If unavailable, inserts one.
# @param panels [Array<Hash>]
# @param metric [PrometheusMetric]
def find_or_create_panel(panels, metric)
panel = find_panel(panels, metric)
return panel if panel
panel = new_panel(metric)
panels << panel
panel
end
# Looks for a metric corresponding to the provided
# metric object. If unavailable, inserts one.
# @param metrics [Array<Hash>]
# @param metric [PrometheusMetric]
def find_or_create_metric(metrics, metric)
target_metric = find_metric(metrics, metric)
return target_metric if target_metric
target_metric = new_metric(metric)
metrics << target_metric
target_metric
end
def find_panel_group(panel_groups, metric)
return unless panel_groups
panel_groups.find { |group| group[:group] == metric.group_title }
end
def find_panel(panels, metric)
return unless panels
panel_identifiers = [DEFAULT_PANEL_TYPE, metric.title, metric.y_label]
panels.find { |panel| panel.values_at(:type, :title, :y_label) == panel_identifiers }
end
def find_metric(metrics, metric)
return unless metrics
metrics.find { |m| m[:id] == metric.identifier }
end
def new_panel_group(metric)
{
group: metric.group_title,
priority: metric.priority,
panels: []
}
end
def new_panel(metric)
{
type: DEFAULT_PANEL_TYPE,
title: metric.title,
y_label: metric.y_label,
metrics: []
}
end
def new_metric(metric)
metric.queries.first.merge(metric_id: metric.id)
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Metrics
module Dashboard
module Stages
class Sorter < BaseStage
def transform!(dashboard)
missing_panel_groups! unless dashboard[:panel_groups].is_a? Array
sort_groups!(dashboard)
sort_panels!(dashboard)
end
private
# Sorts the groups in the dashboard by the :priority key
def sort_groups!(dashboard)
dashboard[:panel_groups] = dashboard[:panel_groups].sort_by { |group| -group[:priority].to_i }
end
# Sorts the panels in the dashboard by the :weight key
def sort_panels!(dashboard)
dashboard[:panel_groups].each do |group|
missing_panels! unless group[:panels].is_a? Array
group[:panels] = group[:panels].sort_by { |panel| -panel[:weight].to_i }
end
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module MetricsDashboard
# Responsible for processesing a dashboard hash, inserting
# relevant DB records & sorting for proper rendering in
# the UI. These includes shared metric info, custom metrics
# info, and alerts (only in EE).
class Processor
SEQUENCE = [
Stages::CommonMetricsInserter,
Stages::ProjectMetricsInserter,
Stages::Sorter
].freeze
def initialize(project, environment)
@project = project
@environment = environment
end
# Returns a new dashboard hash with the results of
# running transforms on the dashboard.
def process(dashboard)
dashboard = dashboard.deep_transform_keys(&:to_sym)
stage_params = [@project, @environment]
sequence.each { |stage| stage.new(*stage_params).transform!(dashboard) }
dashboard
end
private
def sequence
SEQUENCE
end
end
end
end
Gitlab::MetricsDashboard::Processor.prepend EE::Gitlab::MetricsDashboard::Processor
# frozen_string_literal: true
# Fetches the metrics dashboard layout and supplemented the output with DB info.
module Gitlab
module MetricsDashboard
class Service < ::BaseService
SYSTEM_DASHBOARD_NAME = 'common_metrics'
SYSTEM_DASHBOARD_PATH = Rails.root.join('config', 'prometheus', "#{SYSTEM_DASHBOARD_NAME}.yml")
# Returns a DB-supplemented json representation of a dashboard config file.
def get_dashboard
dashboard_string = Rails.cache.fetch(cache_key) { system_dashboard }
dashboard = process_dashboard(dashboard_string)
success(dashboard: dashboard)
rescue Gitlab::MetricsDashboard::Stages::BaseStage::DashboardLayoutError => e
error(e.message, :unprocessable_entity)
end
private
# Returns the base metrics shipped with every GitLab service.
def system_dashboard
YAML.load_file(SYSTEM_DASHBOARD_PATH)
end
def cache_key
"metrics_dashboard_#{SYSTEM_DASHBOARD_NAME}"
end
# Returns a new dashboard Hash, supplemented with DB info
def process_dashboard(dashboard)
Processor.new(project, params[:environment]).process(dashboard)
end
end
end
end
# frozen_string_literal: true
module Gitlab
module MetricsDashboard
module Stages
class BaseStage
DashboardLayoutError = Class.new(StandardError)
DEFAULT_PANEL_TYPE = 'area-chart'
attr_reader :project, :environment
def initialize(project, environment)
@project = project
@environment = environment
end
# Entry-point to the stage
# @param dashboard [Hash]
# @param project [Project]
# @param environment [Environment]
def transform!(_dashboard)
raise NotImplementedError
end
protected
def missing_panel_groups!
raise DashboardLayoutError.new('Top-level key :panel_groups must be an array')
end
def missing_panels!
raise DashboardLayoutError.new('Each "panel_group" must define an array :panels')
end
def missing_metrics!
raise DashboardLayoutError.new('Each "panel" must define an array :metrics')
end
def for_metrics(dashboard)
missing_panel_groups! unless dashboard[:panel_groups].is_a?(Array)
dashboard[:panel_groups].each do |panel_group|
missing_panels! unless panel_group[:panels].is_a?(Array)
panel_group[:panels].each do |panel|
missing_metrics! unless panel[:metrics].is_a?(Array)
panel[:metrics].each do |metric|
yield metric
end
end
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module MetricsDashboard
module Stages
class CommonMetricsInserter < BaseStage
# For each metric in the dashboard config, attempts to
# find a corresponding database record. If found,
# includes the record's id in the dashboard config.
def transform!(dashboard)
common_metrics = ::PrometheusMetric.common
for_metrics(dashboard) do |metric|
metric_record = common_metrics.find { |m| m.identifier == metric[:id] }
metric[:metric_id] = metric_record.id if metric_record
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module MetricsDashboard
module Stages
class ProjectMetricsInserter < BaseStage
# Inserts project-specific metrics into the dashboard
# config. If there are no project-specific metrics,
# this will have no effect.
def transform!(dashboard)
project.prometheus_metrics.each do |project_metric|
group = find_or_create_panel_group(dashboard[:panel_groups], project_metric)
panel = find_or_create_panel(group[:panels], project_metric)
find_or_create_metric(panel[:metrics], project_metric)
end
end
private
# Looks for a panel_group corresponding to the
# provided metric object. If unavailable, inserts one.
# @param panel_groups [Array<Hash>]
# @param metric [PrometheusMetric]
def find_or_create_panel_group(panel_groups, metric)
panel_group = find_panel_group(panel_groups, metric)
return panel_group if panel_group
panel_group = new_panel_group(metric)
panel_groups << panel_group
panel_group
end
# Looks for a panel corresponding to the provided
# metric object. If unavailable, inserts one.
# @param panels [Array<Hash>]
# @param metric [PrometheusMetric]
def find_or_create_panel(panels, metric)
panel = find_panel(panels, metric)
return panel if panel
panel = new_panel(metric)
panels << panel
panel
end
# Looks for a metric corresponding to the provided
# metric object. If unavailable, inserts one.
# @param metrics [Array<Hash>]
# @param metric [PrometheusMetric]
def find_or_create_metric(metrics, metric)
target_metric = find_metric(metrics, metric)
return target_metric if target_metric
target_metric = new_metric(metric)
metrics << target_metric
target_metric
end
def find_panel_group(panel_groups, metric)
return unless panel_groups
panel_groups.find { |group| group[:group] == metric.group_title }
end
def find_panel(panels, metric)
return unless panels
panel_identifiers = [DEFAULT_PANEL_TYPE, metric.title, metric.y_label]
panels.find { |panel| panel.values_at(:type, :title, :y_label) == panel_identifiers }
end
def find_metric(metrics, metric)
return unless metrics
metrics.find { |m| m[:id] == metric.identifier }
end
def new_panel_group(metric)
{
group: metric.group_title,
priority: metric.priority,
panels: []
}
end
def new_panel(metric)
{
type: DEFAULT_PANEL_TYPE,
title: metric.title,
y_label: metric.y_label,
metrics: []
}
end
def new_metric(metric)
metric.queries.first.merge(metric_id: metric.id)
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module MetricsDashboard
module Stages
class Sorter < BaseStage
def transform!(dashboard)
missing_panel_groups! unless dashboard[:panel_groups].is_a? Array
sort_groups!(dashboard)
sort_panels!(dashboard)
end
private
# Sorts the groups in the dashboard by the :priority key
def sort_groups!(dashboard)
dashboard[:panel_groups] = dashboard[:panel_groups].sort_by { |group| -group[:priority].to_i }
end
# Sorts the panels in the dashboard by the :weight key
def sort_panels!(dashboard)
dashboard[:panel_groups].each do |group|
missing_panels! unless group[:panels].is_a? Array
group[:panels] = group[:panels].sort_by { |panel| -panel[:weight].to_i }
end
end
end
end
end
end
......@@ -10,7 +10,7 @@
"priority": { "type": "number" },
"panel_groups": {
"type": "array",
"items": { "$ref": "spec/fixtures/lib/gitlab/metrics_dashboard/schemas/panel_groups.json" }
"items": { "$ref": "spec/fixtures/lib/gitlab/metrics/dashboard/schemas/panel_groups.json" }
}
},
"additionalProperties": false
......
......@@ -2,10 +2,10 @@
require 'spec_helper'
describe Gitlab::MetricsDashboard::Processor do
describe Gitlab::Metrics::Dashboard::Processor do
let(:project) { build(:project) }
let(:environment) { build(:environment) }
let(:dashboard_yml) { YAML.load_file('spec/fixtures/lib/gitlab/metrics_dashboard/sample_dashboard.yml') }
let(:dashboard_yml) { YAML.load_file('spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml') }
describe 'process' do
let(:process_params) { [project, environment] }
......@@ -50,7 +50,7 @@ describe Gitlab::MetricsDashboard::Processor do
shared_examples_for 'errors with message' do |expected_message|
it 'raises a DashboardLayoutError' do
error_class = Gitlab::MetricsDashboard::Stages::BaseStage::DashboardLayoutError
error_class = Gitlab::Metrics::Dashboard::Stages::BaseStage::DashboardLayoutError
expect { dashboard }.to raise_error(error_class, expected_message)
end
......
......@@ -2,12 +2,12 @@
require 'spec_helper'
describe Gitlab::MetricsDashboard::Service, :use_clean_rails_memory_store_caching do
describe Gitlab::Metrics::Dashboard::Service, :use_clean_rails_memory_store_caching do
let(:project) { build(:project) }
let(:environment) { build(:environment) }
describe 'get_dashboard' do
let(:dashboard_schema) { JSON.parse(fixture_file('lib/gitlab/metrics_dashboard/schemas/dashboard.json')) }
let(:dashboard_schema) { JSON.parse(fixture_file('lib/gitlab/metrics/dashboard/schemas/dashboard.json')) }
it 'returns a json representation of the environment dashboard' do
result = described_class.new(project, environment).get_dashboard
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment