lib/release_tools/pipeline_tracer/metrics_service.rb (122 lines of code) (raw):
# frozen_string_literal: true
module ReleaseTools
module PipelineTracer
class MetricsService
include ::SemanticLogger::Loggable
MAX_DEPTH = 3
InvalidDepthError = Class.new(ArgumentError)
def self.from_pipeline_url(pipeline_url, version:, depth: 2, upstream_pipeline_name: nil)
pipeline = Pipeline.from_url(pipeline_url)
new(
pipeline:,
version:,
depth:,
upstream_pipeline_name:
)
end
# @param [PipelineTracer::Pipeline] pipeline
# @param [string] version is the DEPLOY_VERSION.
# @param [integer] depth can be any integer >= 0. It is the depth to which metrics should be generated for this
# pipeline. If depth is 1, metrics will not be generated for jobs in downstream pipelines.
# @return [PipelineTracer::MetricsService]
def initialize(pipeline:, version:, depth: 2, upstream_pipeline_name: nil)
@pipeline = pipeline
@version = version
@depth = depth
@upstream_pipeline_name = upstream_pipeline_name
end
def execute
logger.info('Gathering duration metrics for pipeline', pipeline: pipeline.url)
raise InvalidDepthError, "Depth must be between 0 and #{MAX_DEPTH}" unless depth.between?(0, MAX_DEPTH)
return if SharedStatus.dry_run?
unless pipeline.end_time
logger.info('Not generating duration metrics for pipeline since end_time is unknown', pipeline_url: pipeline.url)
return
end
metrics_client.set('deployment_pipeline_duration_seconds', pipeline.real_time_duration, labels: pipeline_duration_labels)
observe_duration_as_histogram
return if depth < 1
process_pipeline_jobs
process_downstream_pipelines
end
private
attr_reader :pipeline, :version, :depth, :upstream_pipeline_name
def observe_duration_as_histogram
return if pipeline.details.name.nil?
pipeline_name = pipeline.details.name
metric_name = if pipeline.project == Project::ReleaseTools.ops_path && pipeline_name.starts_with?('Deployment pipeline')
'deployment_coordinator_pipeline_duration_seconds'
elsif pipeline.project == Project::OmnibusGitlab.dev_path && pipeline_name == 'AUTO_DEPLOY_BUILD_PIPELINE'
'deployment_packager_omnibus_pipeline_duration_seconds'
elsif pipeline.project == Project::CNGImage.dev_path && %w[AUTO_DEPLOY_BUILD_PIPELINE AUTO_DEPLOY_TAG_BUILD_PIPELINE].include?(pipeline_name)
'deployment_packager_cng_pipeline_duration_seconds'
end
return if metric_name.nil?
metrics_client.observe(metric_name, pipeline.real_time_duration, labels: pipeline_duration_histogram_labels)
end
def process_pipeline_jobs
pipeline.jobs.each_page do |page|
page.each do |job_attributes|
job = Job.new(job_attributes, pipeline.client)
next unless job.completed?
metrics_client.set('deployment_job_duration_seconds', job.real_time_duration, labels: job_duration_labels(job))
process_triggered_pipeline(job)
end
end
end
def process_downstream_pipelines
pipeline.bridge_jobs.each_page do |page|
page.each do |bridge|
next unless bridge.downstream_pipeline
self.class
.from_pipeline_url(
bridge.downstream_pipeline.web_url,
version: version,
depth: depth - 1,
upstream_pipeline_name: pipeline.details.name
)
.execute
end
end
end
def process_triggered_pipeline(job)
return unless job.triggered_downstream_pipeline?
logger.info('Gathering metrics for manually triggered downstream pipeline', downstream_pipeline_url: job.triggered_pipeline_url, job_url: job.web_url)
self.class
.from_pipeline_url(
job.triggered_pipeline_url,
version: version,
depth: depth - 1,
upstream_pipeline_name: pipeline.details.name
)
.execute
end
def job_duration_labels(job)
env = job.environment_from_name
"#{job.name},#{job.stage},#{job.status},#{pipeline.project},#{version}," \
"#{target_env(env)},#{target_stage(env)},#{short_job_name(job)}," \
"#{job.web_url},#{job.id},#{pipeline.details.id},#{pipeline.details.name}"
end
def target_env(environment)
parse_environment(environment)[0]
end
def target_stage(environment)
parse_environment(environment)[1]
end
def parse_environment(environment)
return [] unless environment
return [environment, 'main'] unless environment.end_with?('-cny')
[environment.delete_suffix('-cny'), 'cny']
end
def short_job_name(job)
job.name_without_environment
end
def pipeline_duration_labels
env = pipeline.deploy_environment
"#{pipeline.project},#{version},#{pipeline.details.status},#{pipeline.details.name}," \
"#{pipeline.details.id},#{pipeline.details.web_url},#{target_env(env)}," \
"#{target_stage(env)},#{upstream_pipeline_name}"
end
def pipeline_duration_histogram_labels
pipeline.details.status.to_s
end
def metrics_client
@metrics_client ||= Metrics::Client.new
end
end
end
end