Skip to main content

tracking

CI Minute Tracking and Monitoring

Overview

Effective cost optimization starts with understanding where your compute minutes are being consumed. GitLab provides multiple levels of tracking: namespace, project, pipeline, and job.

Usage Tracking Hierarchy

Namespace (Group/Personal)
 Project A
    Pipeline #123
       Job: build (5 min)
       Job: test (10 min)
       Job: deploy (2 min)
    Pipeline #124
        ...
 Project B
 ...

Viewing Compute Minute Usage

Navigate to: Group Settings Usage Quotas Pipelines

What You See:

  • Total compute minutes used this month
  • Percentage of quota consumed
  • Minutes remaining
  • Historical usage (current month only by default)
  • Projects list sorted by CI/CD minute usage (descending)
  • Includes all projects in the namespace and subgroups

Example:

Total used: 45,000 / 50,000 (90%)
Remaining: 5,000 minutes

Top Projects:
1. agent-mesh: 12,500 min (27.8%)
2. agent-router: 8,300 min (18.4%)
3. platform-agents: 6,200 min (13.8%)
...

For Personal Namespaces

Navigate to: Avatar Settings Usage Quotas

Shows the same information but for your personal projects only.

Project-Level Usage

Navigate to: Project Settings CI/CD Pipelines Usage Statistics

See compute minute consumption for a specific project over time.

Usage Calculation

How Minutes Are Computed

Job Compute Minutes = (Job Duration in Seconds / 60)  Cost Factor
Pipeline Compute Minutes = Sum of all job compute minutes

Important: Jobs can run concurrently, so total compute usage can exceed the pipeline duration.

Example:

# Pipeline runs for 10 minutes end-to-end # But jobs run in parallel: test-unit: # 8 minutes test-e2e: # 10 minutes test-integration: # 6 minutes # Total compute: 8 + 10 + 6 = 24 minutes # Pipeline duration: 10 minutes (wall-clock time)

Cost Factor Impact

Runner TypeCost Factor10-Min Job Cost
Linux Small1x10 minutes
Linux Medium2x20 minutes
Linux Large4x40 minutes
Windows2x20 minutes
macOS6x60 minutes
Self-Hosted0x0 minutes

What Counts Toward Usage

Counted:

  • All jobs running on GitLab-hosted runners
  • Job duration from start to finish (including setup/teardown)
  • Failed jobs (wasted minutes!)
  • Canceled jobs (until cancellation point)

Not Counted:

  • Jobs on self-hosted runners (FREE!)
  • Trigger jobs (no execution, just coordination)
  • Jobs that are skipped by rules
  • Pipeline setup/coordination overhead

Historical Usage Tracking

Monthly Reset

Usage resets on the 1st day of each month. All namespaces start at 0 minutes.

Viewing Past Usage

Current Limitation: GitLab UI shows only the current month by default.

Workaround - API for Historical Data:

# Get namespace usage for a specific month curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ "https://gitlab.com/api/v4/namespaces/:id/ci_minutes" # For specific date range (requires Premium/Ultimate) curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ "https://gitlab.com/api/v4/namespaces/:id/ci_minutes?from=2025-01-01&to=2025-01-31"

Export Usage Data with Miller

# Get current month usage by project glab api /groups/blueflyio/usage_stats | \ jq '.projects[]' | \ mlr --json stats1 -a sum -f ci_minutes -g name | \ mlr --json sort -nr ci_minutes # Track trends over time (save monthly) glab api /groups/blueflyio/usage_stats | \ jq -r '.projects[] | [.name, .ci_minutes] | @csv' >> ci-usage-$(date +%Y-%m).csv

Setting Up Usage Alerts

Built-In Email Notifications

GitLab automatically sends email alerts when:

  • 75% quota reached - Warning notification
  • 95% quota reached - Critical notification
  • 100% quota reached - Quota exhausted notification

Recipients: Namespace owners and maintainers

Configure: Settings Notifications Usage Quotas

Custom Alert Thresholds

For Ultimate Tier:

Create custom monitoring alerts using the GitLab API:

# .gitlab-ci.yml monitor_usage: stage: .pre rules: - if: $CI_PIPELINE_SOURCE == "schedule" script: - | USAGE=$(curl --silent --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ "https://gitlab.com/api/v4/namespaces/$CI_PROJECT_NAMESPACE_ID/ci_minutes" | \ jq '.minutes_used') QUOTA=$(curl --silent --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ "https://gitlab.com/api/v4/namespaces/$CI_PROJECT_NAMESPACE_ID/ci_minutes" | \ jq '.monthly_minutes_limit') PERCENT=$((100 * USAGE / QUOTA)) if [ $PERCENT -gt 80 ]; then echo "WARNING: Using $PERCENT% of CI minutes ($USAGE / $QUOTA)" # Send to Slack/email/etc fi

Schedule this job to run daily or weekly.

Third-Party Monitoring

Datadog Integration:

# Monitor GitLab CI minute usage in Datadog monitors: - type: metric alert query: 'avg(last_1h):gitlab.ci.minutes.used{namespace:blueflyio} > 40000' name: 'GitLab CI Minutes High Usage' message: 'CI minute usage is above 80% of monthly quota'

Prometheus/Grafana:

# Scrape GitLab Runner metrics - job_name: 'gitlab-runner' static_configs: - targets: ['runner.example.com:9252']

Tracking by Pipeline

View Pipeline Compute Usage

Navigate to: Project CI/CD Pipelines Select Pipeline

Details Shown:

  • Total pipeline duration (wall-clock time)
  • Individual job durations
  • Job cost factors
  • Total compute minutes consumed

API Method:

# Get pipeline details including minute usage glab api /projects/:id/pipelines/:pipeline_id | \ jq '.duration, .stages[].jobs[] | {name, duration, runner}'

Identify Expensive Pipelines

# Find pipelines over 100 minutes glab api /projects/:id/pipelines | \ jq '.[] | select(.duration > 6000) | {id, ref, duration, created_at}' # Calculate cost of a specific pipeline glab api /projects/:id/pipelines/:pipeline_id/jobs | \ jq '[.[] | (.duration / 60) * (.runner.cost_factor // 1)] | add'

Tracking by Job

Job-Level Details

Navigate to: Project CI/CD Jobs Select Job

Information Available:

  • Job duration (HH:MM:SS)
  • Runner type and cost factor
  • Compute minutes consumed
  • Artifacts size
  • Cache usage

Find Most Expensive Jobs

# Get all jobs from recent pipelines glab api /projects/:id/pipelines/:pipeline_id/jobs | \ jq -r '.[] | [.name, .duration, .runner.description] | @tsv' | \ sort -k2 -rn | head -20 # Calculate cost per job type glab api /projects/:id/pipelines/:pipeline_id/jobs | \ mlr --json stats1 -a mean,sum -f duration -g name | \ mlr --json put '$cost = $duration_sum / 60' | \ mlr --json sort -nr cost

Jobs That Waste Minutes

Failed Jobs:

# Find frequently failing jobs glab api /projects/:id/pipelines/:pipeline_id/jobs | \ jq '[.[] | select(.status == "failed")] | group_by(.name) | map({name: .[0].name, failures: length})'

Long-Running Jobs:

# Jobs over 30 minutes glab api /projects/:id/jobs | \ jq '.[] | select(.duration > 1800) | {name, duration, pipeline_id}'

Usage Attribution

By Team/Project Group

For Subgroups:

Each subgroup has its own usage quota allocation:

blueflyio/ (Parent Group - 50,000 min)
 agent-platform/ (Subgroup A - inherits quota)
    agent-mesh/
    agent-router/
 ossa/ (Subgroup B - inherits quota)
     openstandardagents/

View subgroup usage: Group Subgroup Settings Usage Quotas

Cost Center Tagging

Tag Projects with Labels:

# .gitlab-ci.yml variables: COST_CENTER: "platform-engineering" TEAM: "agents-team"

Export and analyze:

# Generate cost report by team for project in $(glab repo list --json | jq -r '.[].path_with_namespace'); do TEAM=$(glab api /projects/$(echo $project | sed 's/\//%2F/g')/variables/TEAM 2>/dev/null | jq -r '.value // "untagged"') MINUTES=$(glab api /projects/$(echo $project | sed 's/\//%2F/g') | jq '.statistics.ci_minutes_used // 0') echo "$TEAM,$project,$MINUTES" done | mlr --csv stats1 -a sum -f 3 -g 1

Budget Alerts and Quotas

Purchasing Additional Minutes

When quota runs out:

  • Cost: $10 per 1,000 additional minutes
  • Purchase: Settings Usage Quotas Buy more minutes
  • Validity: 1 year from purchase date
  • Rollover: Purchased minutes roll over month-to-month

Per-Project Limits (Self-Managed)

For GitLab self-managed instances:

Set Project-Specific Quota:

# Rails console project = Project.find_by_full_path('group/project') project.shared_runners_minutes_limit = 5000 project.save

Quota Enforcement

When quota exhausted:

  • Pipelines on shared runners stop immediately
  • Jobs fail with quota exceeded error
  • Self-hosted runners continue working (unaffected)
  • Existing jobs complete, new jobs blocked

API Reference

Essential API Endpoints

# Namespace usage (group or personal) GET /namespaces/:id/ci_minutes # Project statistics GET /projects/:id # Returns: statistics.ci_minutes_used # Pipeline details GET /projects/:id/pipelines/:pipeline_id # Job details GET /projects/:id/jobs GET /projects/:id/jobs/:job_id

Automation Script

#!/bin/bash # ci-usage-report.sh NAMESPACE_ID="12345" ALERT_THRESHOLD=80 USAGE=$(glab api /namespaces/$NAMESPACE_ID/ci_minutes | jq '.minutes_used') QUOTA=$(glab api /namespaces/$NAMESPACE_ID/ci_minutes | jq '.monthly_minutes_limit') PERCENT=$((100 * USAGE / QUOTA)) echo "CI Minute Usage Report - $(date)" echo "================================" echo "Used: $USAGE minutes" echo "Quota: $QUOTA minutes" echo "Percentage: $PERCENT%" echo "" if [ $PERCENT -gt $ALERT_THRESHOLD ]; then echo " WARNING: Usage exceeds ${ALERT_THRESHOLD}% threshold!" echo "" # Get top projects echo "Top 10 Projects by Usage:" glab api /groups/$NAMESPACE_ID/usage_stats | \ jq -r '.projects[] | "\(.name): \(.ci_minutes) min"' | \ sort -t: -k2 -rn | head -10 fi

Monitoring Dashboards

GitLab Ultimate Analytics

Navigate to: Group Analytics CI/CD Analytics

Metrics Available:

  • Pipeline success rate
  • Mean time to detect (MTTD)
  • Mean time to recovery (MTTR)
  • Deployment frequency
  • Compute minute trends (when enabled)

Custom Grafana Dashboard

{ "dashboard": { "title": "GitLab CI Minute Usage", "panels": [ { "title": "Monthly Usage Trend", "targets": [ { "expr": "gitlab_ci_minutes_used{namespace='blueflyio'}" } ] }, { "title": "Top Projects", "targets": [ { "expr": "topk(10, gitlab_ci_minutes_used_per_project)" } ] }, { "title": "Usage vs Quota", "targets": [ { "expr": "gitlab_ci_minutes_used / gitlab_ci_minutes_quota * 100" } ] } ] } }

Troubleshooting

"Usage shows 0 but pipelines ran"

Cause: Jobs ran on self-hosted runners Solution: Check runner tags. Self-hosted runners don't consume quota.

"Can't see historical usage"

Cause: UI only shows current month Solution: Use API to fetch historical data or export monthly reports

"Different numbers in different views"

Cause: Caching delays or timezone differences Solution: Wait a few minutes, refresh, or use API for real-time data

"Charged for failed jobs"

Cause: Jobs consumed runner time before failing Solution: Implement fail-fast patterns and fix failing jobs quickly


Next Steps

  • Strategies - Learn how to reduce minute consumption
  • Monitoring - Set up ongoing tracking and dashboards
  • Checklist - Quick reference for daily checks