diff --git a/.github/badges/dora-metrics.json b/.github/badges/dora-metrics.json new file mode 100644 index 000000000..66e74eb17 --- /dev/null +++ b/.github/badges/dora-metrics.json @@ -0,0 +1,6 @@ +{ + "schemaVersion": 1, + "label": "DORA Metrics", + "message": "Deployment Frequency: High | Lead Time: Medium | Change Failure Rate: Elite | Mean Time to Recovery: N/A", + "color": "blue" +} \ No newline at end of file diff --git a/.github/workflows/metrics.yml b/.github/workflows/metrics.yml new file mode 100644 index 000000000..bcec2528f --- /dev/null +++ b/.github/workflows/metrics.yml @@ -0,0 +1,421 @@ +name: DORA Metrics +on: + # Schedule to run weekly + schedule: + - cron: '0 0 * * 0' # Run at midnight every Sunday + # Allow manual triggering + workflow_dispatch: + +jobs: + collect-metrics: + runs-on: ubuntu-latest + permissions: + actions: read + contents: write # Required to commit badge files + issues: read # Required to analyze bug/incident issues + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up directory for badge files + run: mkdir -p .github/badges + + - name: Calculate DORA Metrics + run: | + # Get date 30 days ago in ISO format + THIRTY_DAYS_AGO=$(date -d "30 days ago" -u +"%Y-%m-%dT%H:%M:%SZ") + + echo "=== Calculating DORA Metrics for Last 30 Days ===" + + #========================================================= + # 1. Deployment Frequency + #========================================================= + echo "Calculating Deployment Frequency..." + + # Get pushes to main branch in last 30 days as a proxy for deployments + # Or use your actual deployment workflow runs if available + DEPLOY_COUNT=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + "https://api.github.com/repos/${{ github.repository }}/commits?since=$THIRTY_DAYS_AGO&sha=main" | \ + jq '. | length') + + echo "Total deployments in last 30 days: $DEPLOY_COUNT" + + if [ -z "$DEPLOY_COUNT" ] || [ "$DEPLOY_COUNT" == "null" ]; then + DEPLOY_COUNT=0 + fi + + # Calculate per day/week/month metrics + if [ "$DEPLOY_COUNT" -gt 0 ]; then + DEPLOY_PER_DAY=$(echo "scale=2; $DEPLOY_COUNT / 30" | bc) + DEPLOY_PER_WEEK=$(echo "scale=2; $DEPLOY_COUNT / 4.3" | bc) + DEPLOY_PER_MONTH="$DEPLOY_COUNT" + + echo "Deployments per day: $DEPLOY_PER_DAY" + echo "Deployments per week: $DEPLOY_PER_WEEK" + + # Determine DORA level based on deployment frequency + if (( $(echo "$DEPLOY_PER_DAY >= 1" | bc -l) )); then + DF_VALUE="$DEPLOY_PER_DAY per day" + DF_LEVEL="Elite" + DF_COLOR="brightgreen" + elif (( $(echo "$DEPLOY_PER_WEEK >= 1" | bc -l) )); then + DF_VALUE="$DEPLOY_PER_WEEK per week" + DF_LEVEL="High" + DF_COLOR="green" + elif (( $(echo "$DEPLOY_PER_MONTH >= 1" | bc -l) )); then + DF_VALUE="$DEPLOY_PER_MONTH per month" + DF_LEVEL="Medium" + DF_COLOR="yellow" + else + DF_VALUE="$DEPLOY_PER_MONTH per month" + DF_LEVEL="Low" + DF_COLOR="red" + fi + else + DF_VALUE="0 per month" + DF_LEVEL="Low" + DF_COLOR="red" + fi + + echo "Deployment Frequency: $DF_VALUE ($DF_LEVEL)" + + #========================================================= + # 2. Lead Time for Changes + #========================================================= + echo "Calculating Lead Time for Changes..." + + # Get merged PRs in the last 30 days + MERGED_PRS=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + "https://api.github.com/repos/${{ github.repository }}/pulls?state=closed&sort=updated&direction=desc&per_page=100" | \ + jq '[.[] | select(.merged_at != null and .merged_at > "'$THIRTY_DAYS_AGO'")]') + + PR_COUNT=$(echo "$MERGED_PRS" | jq 'length') + echo "Merged PRs in last 30 days: $PR_COUNT" + + if [ "$PR_COUNT" -gt 0 ]; then + TOTAL_HOURS=0 + + for i in $(seq 0 $(($PR_COUNT-1))); do + CREATED=$(echo "$MERGED_PRS" | jq -r ".[$i].created_at") + MERGED=$(echo "$MERGED_PRS" | jq -r ".[$i].merged_at") + + CREATED_TS=$(date -d "$CREATED" +%s) + MERGED_TS=$(date -d "$MERGED" +%s) + + DIFF_SECS=$(($MERGED_TS - $CREATED_TS)) + PR_HOURS=$(echo "scale=2; $DIFF_SECS / 3600" | bc) + + TOTAL_HOURS=$(echo "scale=2; $TOTAL_HOURS + $PR_HOURS" | bc) + done + + AVG_HOURS=$(echo "scale=2; $TOTAL_HOURS / $PR_COUNT" | bc) + echo "Average lead time: $AVG_HOURS hours" + + # Determine DORA level based on lead time + if (( $(echo "$AVG_HOURS < 24" | bc -l) )); then + LT_VALUE="$AVG_HOURS hours" + LT_LEVEL="Elite" + LT_COLOR="brightgreen" + elif (( $(echo "$AVG_HOURS < 168" | bc -l) )); then + AVG_DAYS=$(echo "scale=2; $AVG_HOURS / 24" | bc) + LT_VALUE="$AVG_DAYS days" + LT_LEVEL="High" + LT_COLOR="green" + elif (( $(echo "$AVG_HOURS < 730" | bc -l) )); then + AVG_DAYS=$(echo "scale=2; $AVG_HOURS / 24" | bc) + LT_VALUE="$AVG_DAYS days" + LT_LEVEL="Medium" + LT_COLOR="yellow" + else + AVG_DAYS=$(echo "scale=2; $AVG_HOURS / 24" | bc) + LT_VALUE="$AVG_DAYS days" + LT_LEVEL="Low" + LT_COLOR="red" + fi + else + LT_VALUE="No PRs" + LT_LEVEL="N/A" + LT_COLOR="gray" + fi + + echo "Lead Time: $LT_VALUE ($LT_LEVEL)" + + #========================================================= + # 3. Change Failure Rate + #========================================================= + echo "Calculating Change Failure Rate..." + + # Get bugs/incidents reported in the last 30 days + # (Using issues with "bug" or "incident" labels) + BUG_COUNT=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + "https://api.github.com/repos/${{ github.repository }}/issues?state=all&labels=bug&since=$THIRTY_DAYS_AGO&per_page=100" | \ + jq '. | length') + + INCIDENT_COUNT=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + "https://api.github.com/repos/${{ github.repository }}/issues?state=all&labels=incident&since=$THIRTY_DAYS_AGO&per_page=100" | \ + jq '. | length') + + if [ -z "$BUG_COUNT" ] || [ "$BUG_COUNT" == "null" ]; then + BUG_COUNT=0 + fi + + if [ -z "$INCIDENT_COUNT" ] || [ "$INCIDENT_COUNT" == "null" ]; then + INCIDENT_COUNT=0 + fi + + TOTAL_FAILURES=$(($BUG_COUNT + $INCIDENT_COUNT)) + echo "Bugs/incidents in last 30 days: $TOTAL_FAILURES" + + if [ "$DEPLOY_COUNT" -gt 0 ]; then + FAILURE_RATE=$(echo "scale=2; $TOTAL_FAILURES / $DEPLOY_COUNT * 100" | bc) + echo "Change failure rate: $FAILURE_RATE%" + + # Determine DORA level based on failure rate + if (( $(echo "$FAILURE_RATE <= 15" | bc -l) )); then + CFR_VALUE="$FAILURE_RATE%" + CFR_LEVEL="Elite" + CFR_COLOR="brightgreen" + elif (( $(echo "$FAILURE_RATE <= 30" | bc -l) )); then + CFR_VALUE="$FAILURE_RATE%" + CFR_LEVEL="High" + CFR_COLOR="green" + elif (( $(echo "$FAILURE_RATE <= 45" | bc -l) )); then + CFR_VALUE="$FAILURE_RATE%" + CFR_LEVEL="Medium" + CFR_COLOR="yellow" + else + CFR_VALUE="$FAILURE_RATE%" + CFR_LEVEL="Low" + CFR_COLOR="red" + fi + else + CFR_VALUE="N/A (No deployments)" + CFR_LEVEL="N/A" + CFR_COLOR="gray" + fi + + echo "Change Failure Rate: $CFR_VALUE ($CFR_LEVEL)" + + #========================================================= + # 4. Mean Time to Recovery (MTTR) + #========================================================= + echo "Calculating Mean Time to Recovery..." + + # Get closed incidents in the last 30 days + CLOSED_INCIDENTS=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + "https://api.github.com/repos/${{ github.repository }}/issues?state=closed&labels=incident&per_page=100" | \ + jq '[.[] | select(.closed_at > "'$THIRTY_DAYS_AGO'")]') + + INCIDENT_COUNT=$(echo "$CLOSED_INCIDENTS" | jq 'length') + echo "Closed incidents in last 30 days: $INCIDENT_COUNT" + + if [ "$INCIDENT_COUNT" -gt 0 ]; then + TOTAL_MINUTES=0 + + for i in $(seq 0 $(($INCIDENT_COUNT-1))); do + CREATED=$(echo "$CLOSED_INCIDENTS" | jq -r ".[$i].created_at") + CLOSED=$(echo "$CLOSED_INCIDENTS" | jq -r ".[$i].closed_at") + + CREATED_TS=$(date -d "$CREATED" +%s) + CLOSED_TS=$(date -d "$CLOSED" +%s) + + DIFF_MINS=$(echo "scale=2; ($CLOSED_TS - $CREATED_TS) / 60" | bc) + TOTAL_MINUTES=$(echo "scale=2; $TOTAL_MINUTES + $DIFF_MINS" | bc) + done + + AVG_MINUTES=$(echo "scale=2; $TOTAL_MINUTES / $INCIDENT_COUNT" | bc) + AVG_HOURS=$(echo "scale=2; $AVG_MINUTES / 60" | bc) + echo "Average recovery time: $AVG_HOURS hours" + + # Determine DORA level based on recovery time + if (( $(echo "$AVG_HOURS < 1" | bc -l) )); then + MTTR_VALUE="$AVG_MINUTES minutes" + MTTR_LEVEL="Elite" + MTTR_COLOR="brightgreen" + elif (( $(echo "$AVG_HOURS < 24" | bc -l) )); then + MTTR_VALUE="$AVG_HOURS hours" + MTTR_LEVEL="High" + MTTR_COLOR="green" + elif (( $(echo "$AVG_HOURS < 168" | bc -l) )); then + MTTR_VALUE="$AVG_HOURS hours" + MTTR_LEVEL="Medium" + MTTR_COLOR="yellow" + else + AVG_DAYS=$(echo "scale=2; $AVG_HOURS / 24" | bc) + MTTR_VALUE="$AVG_DAYS days" + MTTR_LEVEL="Low" + MTTR_COLOR="red" + fi + else + MTTR_VALUE="N/A (No incidents)" + MTTR_LEVEL="N/A" + MTTR_COLOR="gray" + fi + + echo "Mean Time to Recovery: $MTTR_VALUE ($MTTR_LEVEL)" + + #========================================================= + # Generate Badge Files + #========================================================= + echo "Generating badge files..." + + # Deployment Frequency Badge + cat > .github/badges/deployment-frequency.json << EOF + { + "schemaVersion": 1, + "label": "Deployment Frequency", + "message": "$DF_VALUE ($DF_LEVEL)", + "color": "$DF_COLOR" + } + EOF + + # Lead Time Badge + cat > .github/badges/lead-time.json << EOF + { + "schemaVersion": 1, + "label": "Lead Time", + "message": "$LT_VALUE ($LT_LEVEL)", + "color": "$LT_COLOR" + } + EOF + + # Change Failure Rate Badge + cat > .github/badges/change-failure-rate.json << EOF + { + "schemaVersion": 1, + "label": "Change Failure Rate", + "message": "$CFR_VALUE ($CFR_LEVEL)", + "color": "$CFR_COLOR" + } + EOF + + # MTTR Badge + cat > .github/badges/mttr.json << EOF + { + "schemaVersion": 1, + "label": "MTTR", + "message": "$MTTR_VALUE ($MTTR_LEVEL)", + "color": "$MTTR_COLOR" + } + EOF + + # Combined DORA Metrics Badge + cat > .github/badges/dora-metrics.json << EOF + { + "schemaVersion": 1, + "label": "DORA Metrics", + "message": "Deployment Frequency: $DF_LEVEL | Lead Time: $LT_LEVEL | Change Failure Rate: $CFR_LEVEL | Mean Time to Recovery: $MTTR_LEVEL", + "color": "blue" + } + EOF + + echo "Badge files generated successfully!" + + #========================================================= + # Generate Metrics Report + #========================================================= + echo "Generating metrics report..." + + cat > dora-metrics-report.md << EOF + # DORA Metrics Report + + *Generated on $(date)* + + ## Summary + + | Metric | Value | Performance Level | + |--------|-------|------------------| + | Deployment Frequency | $DF_VALUE | $DF_LEVEL | + | Lead Time for Changes | $LT_VALUE | $LT_LEVEL | + | Change Failure Rate | $CFR_VALUE | $CFR_LEVEL | + | Mean Time to Recovery | $MTTR_VALUE | $MTTR_LEVEL | + + ## How to Add Badges to Your README + + Add the following to your README.md: + + \`\`\`markdown + ![Deployment Frequency](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/${{ github.repository }}/main/.github/badges/deployment-frequency.json) + ![Lead Time](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/${{ github.repository }}/main/.github/badges/lead-time.json) + ![Change Failure Rate](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/${{ github.repository }}/main/.github/badges/change-failure-rate.json) + ![MTTR](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/${{ github.repository }}/main/.github/badges/mttr.json) + ![DORA Metrics](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/${{ github.repository }}/main/.github/badges/dora-metrics.json) + \`\`\` + + ## Details + + ### Deployment Frequency + + - Total deployments in last 30 days: $DEPLOY_COUNT + - Deployments per day: $DEPLOY_PER_DAY + - Performance level: $DF_LEVEL + + ### Lead Time for Changes + + - Merged PRs in last 30 days: $PR_COUNT + - Average lead time: $LT_VALUE + - Performance level: $LT_LEVEL + + ### Change Failure Rate + + - Total failures in last 30 days: $TOTAL_FAILURES + - Change failure rate: $CFR_VALUE + - Performance level: $CFR_LEVEL + + ### Mean Time to Recovery + + - Closed incidents in last 30 days: $INCIDENT_COUNT + - Average recovery time: $MTTR_VALUE + - Performance level: $MTTR_LEVEL + EOF + + echo "Metrics report generated successfully!" + + - name: Create GitHub Issue with Report + run: | + REPORT=$(cat dora-metrics-report.md) + TODAY=$(date +"%Y-%m-%d") + + # Create or update issue with metrics report + ISSUE_DATA=$(cat << EOF + { + "title": "DORA Metrics Report - $TODAY", + "body": $REPORT, + "labels": ["metrics", "dora"] + } + EOF + ) + + # Try to find existing open issue with the same label + EXISTING_ISSUES=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + "https://api.github.com/repos/${{ github.repository }}/issues?state=open&labels=dora") + + ISSUE_COUNT=$(echo "$EXISTING_ISSUES" | jq 'length') + + if [ "$ISSUE_COUNT" -gt 0 ]; then + # Update existing issue + ISSUE_NUMBER=$(echo "$EXISTING_ISSUES" | jq -r '.[0].number') + echo "Updating existing issue #$ISSUE_NUMBER" + + curl -s -X PATCH -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + -H "Accept: application/vnd.github.v3+json" \ + -d "$ISSUE_DATA" \ + "https://api.github.com/repos/${{ github.repository }}/issues/$ISSUE_NUMBER" + else + # Create new issue + echo "Creating new DORA metrics issue" + + curl -s -X POST -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + -H "Accept: application/vnd.github.v3+json" \ + -d "$ISSUE_DATA" \ + "https://api.github.com/repos/${{ github.repository }}/issues" + fi + + - name: Commit badge files + run: | + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git add .github/badges/ + git commit -m "Update DORA metrics badges [skip ci]" || echo "No changes to commit" + git push \ No newline at end of file diff --git a/README.md b/README.md index da15154e3..b8c6794ba 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ -[![SLIM](https://img.shields.io/badge/Best%20Practices%20from-SLIM-blue)](https://nasa-ammos.github.io/slim/) +[![SLIM](https://img.shields.io/badge/Best%20Practices%20from-SLIM-blue)](https://nasa-ammos.github.io/slim/) ![DORA Metrics](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/NASA-AMMOS/slim/main/.github/badges/dora-metrics.json) ![screen-slim](https://github.com/NASA-AMMOS/slim/assets/3129134/d4da5150-aae6-4986-b18e-5c463f8ff38a) diff --git a/docs/guides/software-lifecycle/metrics/README.md b/docs/guides/software-lifecycle/metrics/README.md index 743f11ef3..40ab69269 100644 --- a/docs/guides/software-lifecycle/metrics/README.md +++ b/docs/guides/software-lifecycle/metrics/README.md @@ -7,29 +7,51 @@ ## Introduction -Metrics collection is important for project management and software quality assurance. We recommend [Apache DevLake](https://devlake.apache.org/) for easy tracking and analysis. This guide simplifies its installation and configuration, especially for developers new to metrics collection. +Metrics collection is important for project management and software quality assurance. This guide provides two tool recommendations for collecting and visualizing metrics, depending on the complexity of your project: +- For complex, multi-repository metrics gathering: We recommend [Apache DevLake](https://devlake.apache.org/) for detailed metrics tracking and analysis. +- For simpler, single-repository projects: We recommend using GitHub Actions-based tools for easy integration and basic metrics collection. Specifically, the [DeveloperMetrics GitHub Action](https://github.com/DeveloperMetrics), which enables you to quickly gather and display DORA metrics with minimal configuration. **Use Cases**: - Collecting and analyzing [DORA metrics](https://devlake.apache.org/docs/DORA/) along with many others for your project. - Creating a visual dashboard to view metrics from multiple sources (e.g., GitHub, JIRA) in one place. -- Streamlining the setup and configuration of Apache DevLake through a *single-command* setup step. -- Gain insight into organizational and project performance for software development and the overall software lifecycle. +- Streamlining the setup and configuration of metrics collection through a single-command setup step or plug-and-play GitHub Action. +- Simplifying metrics collection for smaller projects to encourage broader adoption. -**Why We Chose Apache DevLake:** -Our decision to select Apache DevLake was informed by thorough trade study documentation, available [here](https://github.com/NASA-AMMOS/slim/issues/117#issuecomment-1802302091). +## Tool Options ---- +### 1. **Apache DevLake** for Complex Multi-Repository Projects -## Prerequisites +DevLake is a powerful and flexible tool for gathering and visualizing software lifecycle metrics across multiple repositories. It requires Docker and some configuration but is ideal for teams managing several repositories or those needing in-depth metrics analysis. +**Prerequisites**: - Familiarity with [Docker](https://docs.docker.com/engine/install/) as well as a running instance of it - A familiarity with validated software metrics is not required for this tool but it is recommended +**Why We Chose Apache DevLake**: + +- Our decision to select Apache DevLake was informed by thorough trade study documentation, available [here](https://github.com/NASA-AMMOS/slim/issues/117#issuecomment-1802302091). +- Offers detailed metrics analysis for complex projects. +- Supports various data sources (GitHub, Jira, etc.) and can create powerful visual dashboards. +- Well-suited for projects requiring a deeper understanding of organizational and project performance. + + +### 2. **DeveloperMetrics GitHub Action** for Simple Single-Repository Projects + +For smaller, simpler projects, where full DevLake setup might be too cumbersome, we recommend the [DeveloperMetrics GitHub Action](https://github.com/DeveloperMetrics). This tool allows you to quickly collect basic DORA metrics (e.g., deployment frequency, lead time for changes) and display results as badges in your project README. + +**Why We Recommend DeveloperMetrics**: + +- Lightweight, quick to integrate into any GitHub repository via GitHub Actions. +- No server setup or Docker required—ideal for developers who want an easy solution. +- Automatically collects basic DORA metrics, with results visible as badges on your repository. +- SLIM can automatically submit PRs to infuse this action into repositories, making it easier to adopt. + + --- -## Quick Start +## Quick Start: Apache DevLake To quickly deploy DevLake on one of your servers or locally for testing, we've developed a convenient 1-step command. Please ensure Docker is running on your system before executing this command. @@ -68,9 +90,26 @@ cd /path/to/your/chosen/deployment/directory The `-d` flag runs containers in detached mode, allowing them to run in the background. +--- +## Quick Start: DeveloperMetrics GitHub Action + +To add simple metrics collection for a single repository: + +1. Add the following GitHub Action to your repository's [`.github/workflows/metrics.yml` file](/static/assets/software-lifecycle/metrics/metrics.yml): + +2. After a push to your repository, metrics such as deployment frequency and lead time for changes are automatically computed, and the results are saved to `.github/badges/` as JOSN files. + +3. Add badges to your project `README.md` to showcase metrics. +``` +![DORA Metrics](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com///main/.github/badges/dora-metrics.json) +``` +For example: ![DORA Metrics](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/yunks128/slim-leaderboard/main/.github/badges/dora-metrics.json) ![Deployment Frequency](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/yunks128/slim-leaderboard/main/.github/badges/deployment-frequency.json) ![Change Failure Rate](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/yunks128/slim-leaderboard/main/.github/badges/change-failure-rate.json) + + --- -## Step-by-Step Configuration Guide +## Step-by-Step Configuration Guide for DevLake +_For **DeveloperMetrics**, no additional configuration is needed beyond adding the GitHub Action to your repository._ 1. Run the **[Quick Start](#quick-start)** steps above. 2. Once you have a working DevLake instance, we recommend going through DevLake's [official start guide](https://devlake.apache.org/docs/Overview/Introduction/#2-configuring-data-source) step-by-step, beginning with the data sources section. @@ -109,6 +148,7 @@ See [this list](https://devlake.apache.org/docs/Metrics) of metrics on the DevLa - Dillon Dalton [ddalton-jpl](https://github.com/ddalton-jpl) - Rishi Verma [riverma](https://github.com/riverma) +- Kyongsik Yun [yunks128](https://github.com/yunks128) --- diff --git a/static/assets/software-lifecycle/metrics/metrics.yml b/static/assets/software-lifecycle/metrics/metrics.yml new file mode 100644 index 000000000..bcec2528f --- /dev/null +++ b/static/assets/software-lifecycle/metrics/metrics.yml @@ -0,0 +1,421 @@ +name: DORA Metrics +on: + # Schedule to run weekly + schedule: + - cron: '0 0 * * 0' # Run at midnight every Sunday + # Allow manual triggering + workflow_dispatch: + +jobs: + collect-metrics: + runs-on: ubuntu-latest + permissions: + actions: read + contents: write # Required to commit badge files + issues: read # Required to analyze bug/incident issues + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up directory for badge files + run: mkdir -p .github/badges + + - name: Calculate DORA Metrics + run: | + # Get date 30 days ago in ISO format + THIRTY_DAYS_AGO=$(date -d "30 days ago" -u +"%Y-%m-%dT%H:%M:%SZ") + + echo "=== Calculating DORA Metrics for Last 30 Days ===" + + #========================================================= + # 1. Deployment Frequency + #========================================================= + echo "Calculating Deployment Frequency..." + + # Get pushes to main branch in last 30 days as a proxy for deployments + # Or use your actual deployment workflow runs if available + DEPLOY_COUNT=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + "https://api.github.com/repos/${{ github.repository }}/commits?since=$THIRTY_DAYS_AGO&sha=main" | \ + jq '. | length') + + echo "Total deployments in last 30 days: $DEPLOY_COUNT" + + if [ -z "$DEPLOY_COUNT" ] || [ "$DEPLOY_COUNT" == "null" ]; then + DEPLOY_COUNT=0 + fi + + # Calculate per day/week/month metrics + if [ "$DEPLOY_COUNT" -gt 0 ]; then + DEPLOY_PER_DAY=$(echo "scale=2; $DEPLOY_COUNT / 30" | bc) + DEPLOY_PER_WEEK=$(echo "scale=2; $DEPLOY_COUNT / 4.3" | bc) + DEPLOY_PER_MONTH="$DEPLOY_COUNT" + + echo "Deployments per day: $DEPLOY_PER_DAY" + echo "Deployments per week: $DEPLOY_PER_WEEK" + + # Determine DORA level based on deployment frequency + if (( $(echo "$DEPLOY_PER_DAY >= 1" | bc -l) )); then + DF_VALUE="$DEPLOY_PER_DAY per day" + DF_LEVEL="Elite" + DF_COLOR="brightgreen" + elif (( $(echo "$DEPLOY_PER_WEEK >= 1" | bc -l) )); then + DF_VALUE="$DEPLOY_PER_WEEK per week" + DF_LEVEL="High" + DF_COLOR="green" + elif (( $(echo "$DEPLOY_PER_MONTH >= 1" | bc -l) )); then + DF_VALUE="$DEPLOY_PER_MONTH per month" + DF_LEVEL="Medium" + DF_COLOR="yellow" + else + DF_VALUE="$DEPLOY_PER_MONTH per month" + DF_LEVEL="Low" + DF_COLOR="red" + fi + else + DF_VALUE="0 per month" + DF_LEVEL="Low" + DF_COLOR="red" + fi + + echo "Deployment Frequency: $DF_VALUE ($DF_LEVEL)" + + #========================================================= + # 2. Lead Time for Changes + #========================================================= + echo "Calculating Lead Time for Changes..." + + # Get merged PRs in the last 30 days + MERGED_PRS=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + "https://api.github.com/repos/${{ github.repository }}/pulls?state=closed&sort=updated&direction=desc&per_page=100" | \ + jq '[.[] | select(.merged_at != null and .merged_at > "'$THIRTY_DAYS_AGO'")]') + + PR_COUNT=$(echo "$MERGED_PRS" | jq 'length') + echo "Merged PRs in last 30 days: $PR_COUNT" + + if [ "$PR_COUNT" -gt 0 ]; then + TOTAL_HOURS=0 + + for i in $(seq 0 $(($PR_COUNT-1))); do + CREATED=$(echo "$MERGED_PRS" | jq -r ".[$i].created_at") + MERGED=$(echo "$MERGED_PRS" | jq -r ".[$i].merged_at") + + CREATED_TS=$(date -d "$CREATED" +%s) + MERGED_TS=$(date -d "$MERGED" +%s) + + DIFF_SECS=$(($MERGED_TS - $CREATED_TS)) + PR_HOURS=$(echo "scale=2; $DIFF_SECS / 3600" | bc) + + TOTAL_HOURS=$(echo "scale=2; $TOTAL_HOURS + $PR_HOURS" | bc) + done + + AVG_HOURS=$(echo "scale=2; $TOTAL_HOURS / $PR_COUNT" | bc) + echo "Average lead time: $AVG_HOURS hours" + + # Determine DORA level based on lead time + if (( $(echo "$AVG_HOURS < 24" | bc -l) )); then + LT_VALUE="$AVG_HOURS hours" + LT_LEVEL="Elite" + LT_COLOR="brightgreen" + elif (( $(echo "$AVG_HOURS < 168" | bc -l) )); then + AVG_DAYS=$(echo "scale=2; $AVG_HOURS / 24" | bc) + LT_VALUE="$AVG_DAYS days" + LT_LEVEL="High" + LT_COLOR="green" + elif (( $(echo "$AVG_HOURS < 730" | bc -l) )); then + AVG_DAYS=$(echo "scale=2; $AVG_HOURS / 24" | bc) + LT_VALUE="$AVG_DAYS days" + LT_LEVEL="Medium" + LT_COLOR="yellow" + else + AVG_DAYS=$(echo "scale=2; $AVG_HOURS / 24" | bc) + LT_VALUE="$AVG_DAYS days" + LT_LEVEL="Low" + LT_COLOR="red" + fi + else + LT_VALUE="No PRs" + LT_LEVEL="N/A" + LT_COLOR="gray" + fi + + echo "Lead Time: $LT_VALUE ($LT_LEVEL)" + + #========================================================= + # 3. Change Failure Rate + #========================================================= + echo "Calculating Change Failure Rate..." + + # Get bugs/incidents reported in the last 30 days + # (Using issues with "bug" or "incident" labels) + BUG_COUNT=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + "https://api.github.com/repos/${{ github.repository }}/issues?state=all&labels=bug&since=$THIRTY_DAYS_AGO&per_page=100" | \ + jq '. | length') + + INCIDENT_COUNT=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + "https://api.github.com/repos/${{ github.repository }}/issues?state=all&labels=incident&since=$THIRTY_DAYS_AGO&per_page=100" | \ + jq '. | length') + + if [ -z "$BUG_COUNT" ] || [ "$BUG_COUNT" == "null" ]; then + BUG_COUNT=0 + fi + + if [ -z "$INCIDENT_COUNT" ] || [ "$INCIDENT_COUNT" == "null" ]; then + INCIDENT_COUNT=0 + fi + + TOTAL_FAILURES=$(($BUG_COUNT + $INCIDENT_COUNT)) + echo "Bugs/incidents in last 30 days: $TOTAL_FAILURES" + + if [ "$DEPLOY_COUNT" -gt 0 ]; then + FAILURE_RATE=$(echo "scale=2; $TOTAL_FAILURES / $DEPLOY_COUNT * 100" | bc) + echo "Change failure rate: $FAILURE_RATE%" + + # Determine DORA level based on failure rate + if (( $(echo "$FAILURE_RATE <= 15" | bc -l) )); then + CFR_VALUE="$FAILURE_RATE%" + CFR_LEVEL="Elite" + CFR_COLOR="brightgreen" + elif (( $(echo "$FAILURE_RATE <= 30" | bc -l) )); then + CFR_VALUE="$FAILURE_RATE%" + CFR_LEVEL="High" + CFR_COLOR="green" + elif (( $(echo "$FAILURE_RATE <= 45" | bc -l) )); then + CFR_VALUE="$FAILURE_RATE%" + CFR_LEVEL="Medium" + CFR_COLOR="yellow" + else + CFR_VALUE="$FAILURE_RATE%" + CFR_LEVEL="Low" + CFR_COLOR="red" + fi + else + CFR_VALUE="N/A (No deployments)" + CFR_LEVEL="N/A" + CFR_COLOR="gray" + fi + + echo "Change Failure Rate: $CFR_VALUE ($CFR_LEVEL)" + + #========================================================= + # 4. Mean Time to Recovery (MTTR) + #========================================================= + echo "Calculating Mean Time to Recovery..." + + # Get closed incidents in the last 30 days + CLOSED_INCIDENTS=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + "https://api.github.com/repos/${{ github.repository }}/issues?state=closed&labels=incident&per_page=100" | \ + jq '[.[] | select(.closed_at > "'$THIRTY_DAYS_AGO'")]') + + INCIDENT_COUNT=$(echo "$CLOSED_INCIDENTS" | jq 'length') + echo "Closed incidents in last 30 days: $INCIDENT_COUNT" + + if [ "$INCIDENT_COUNT" -gt 0 ]; then + TOTAL_MINUTES=0 + + for i in $(seq 0 $(($INCIDENT_COUNT-1))); do + CREATED=$(echo "$CLOSED_INCIDENTS" | jq -r ".[$i].created_at") + CLOSED=$(echo "$CLOSED_INCIDENTS" | jq -r ".[$i].closed_at") + + CREATED_TS=$(date -d "$CREATED" +%s) + CLOSED_TS=$(date -d "$CLOSED" +%s) + + DIFF_MINS=$(echo "scale=2; ($CLOSED_TS - $CREATED_TS) / 60" | bc) + TOTAL_MINUTES=$(echo "scale=2; $TOTAL_MINUTES + $DIFF_MINS" | bc) + done + + AVG_MINUTES=$(echo "scale=2; $TOTAL_MINUTES / $INCIDENT_COUNT" | bc) + AVG_HOURS=$(echo "scale=2; $AVG_MINUTES / 60" | bc) + echo "Average recovery time: $AVG_HOURS hours" + + # Determine DORA level based on recovery time + if (( $(echo "$AVG_HOURS < 1" | bc -l) )); then + MTTR_VALUE="$AVG_MINUTES minutes" + MTTR_LEVEL="Elite" + MTTR_COLOR="brightgreen" + elif (( $(echo "$AVG_HOURS < 24" | bc -l) )); then + MTTR_VALUE="$AVG_HOURS hours" + MTTR_LEVEL="High" + MTTR_COLOR="green" + elif (( $(echo "$AVG_HOURS < 168" | bc -l) )); then + MTTR_VALUE="$AVG_HOURS hours" + MTTR_LEVEL="Medium" + MTTR_COLOR="yellow" + else + AVG_DAYS=$(echo "scale=2; $AVG_HOURS / 24" | bc) + MTTR_VALUE="$AVG_DAYS days" + MTTR_LEVEL="Low" + MTTR_COLOR="red" + fi + else + MTTR_VALUE="N/A (No incidents)" + MTTR_LEVEL="N/A" + MTTR_COLOR="gray" + fi + + echo "Mean Time to Recovery: $MTTR_VALUE ($MTTR_LEVEL)" + + #========================================================= + # Generate Badge Files + #========================================================= + echo "Generating badge files..." + + # Deployment Frequency Badge + cat > .github/badges/deployment-frequency.json << EOF + { + "schemaVersion": 1, + "label": "Deployment Frequency", + "message": "$DF_VALUE ($DF_LEVEL)", + "color": "$DF_COLOR" + } + EOF + + # Lead Time Badge + cat > .github/badges/lead-time.json << EOF + { + "schemaVersion": 1, + "label": "Lead Time", + "message": "$LT_VALUE ($LT_LEVEL)", + "color": "$LT_COLOR" + } + EOF + + # Change Failure Rate Badge + cat > .github/badges/change-failure-rate.json << EOF + { + "schemaVersion": 1, + "label": "Change Failure Rate", + "message": "$CFR_VALUE ($CFR_LEVEL)", + "color": "$CFR_COLOR" + } + EOF + + # MTTR Badge + cat > .github/badges/mttr.json << EOF + { + "schemaVersion": 1, + "label": "MTTR", + "message": "$MTTR_VALUE ($MTTR_LEVEL)", + "color": "$MTTR_COLOR" + } + EOF + + # Combined DORA Metrics Badge + cat > .github/badges/dora-metrics.json << EOF + { + "schemaVersion": 1, + "label": "DORA Metrics", + "message": "Deployment Frequency: $DF_LEVEL | Lead Time: $LT_LEVEL | Change Failure Rate: $CFR_LEVEL | Mean Time to Recovery: $MTTR_LEVEL", + "color": "blue" + } + EOF + + echo "Badge files generated successfully!" + + #========================================================= + # Generate Metrics Report + #========================================================= + echo "Generating metrics report..." + + cat > dora-metrics-report.md << EOF + # DORA Metrics Report + + *Generated on $(date)* + + ## Summary + + | Metric | Value | Performance Level | + |--------|-------|------------------| + | Deployment Frequency | $DF_VALUE | $DF_LEVEL | + | Lead Time for Changes | $LT_VALUE | $LT_LEVEL | + | Change Failure Rate | $CFR_VALUE | $CFR_LEVEL | + | Mean Time to Recovery | $MTTR_VALUE | $MTTR_LEVEL | + + ## How to Add Badges to Your README + + Add the following to your README.md: + + \`\`\`markdown + ![Deployment Frequency](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/${{ github.repository }}/main/.github/badges/deployment-frequency.json) + ![Lead Time](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/${{ github.repository }}/main/.github/badges/lead-time.json) + ![Change Failure Rate](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/${{ github.repository }}/main/.github/badges/change-failure-rate.json) + ![MTTR](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/${{ github.repository }}/main/.github/badges/mttr.json) + ![DORA Metrics](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/${{ github.repository }}/main/.github/badges/dora-metrics.json) + \`\`\` + + ## Details + + ### Deployment Frequency + + - Total deployments in last 30 days: $DEPLOY_COUNT + - Deployments per day: $DEPLOY_PER_DAY + - Performance level: $DF_LEVEL + + ### Lead Time for Changes + + - Merged PRs in last 30 days: $PR_COUNT + - Average lead time: $LT_VALUE + - Performance level: $LT_LEVEL + + ### Change Failure Rate + + - Total failures in last 30 days: $TOTAL_FAILURES + - Change failure rate: $CFR_VALUE + - Performance level: $CFR_LEVEL + + ### Mean Time to Recovery + + - Closed incidents in last 30 days: $INCIDENT_COUNT + - Average recovery time: $MTTR_VALUE + - Performance level: $MTTR_LEVEL + EOF + + echo "Metrics report generated successfully!" + + - name: Create GitHub Issue with Report + run: | + REPORT=$(cat dora-metrics-report.md) + TODAY=$(date +"%Y-%m-%d") + + # Create or update issue with metrics report + ISSUE_DATA=$(cat << EOF + { + "title": "DORA Metrics Report - $TODAY", + "body": $REPORT, + "labels": ["metrics", "dora"] + } + EOF + ) + + # Try to find existing open issue with the same label + EXISTING_ISSUES=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + "https://api.github.com/repos/${{ github.repository }}/issues?state=open&labels=dora") + + ISSUE_COUNT=$(echo "$EXISTING_ISSUES" | jq 'length') + + if [ "$ISSUE_COUNT" -gt 0 ]; then + # Update existing issue + ISSUE_NUMBER=$(echo "$EXISTING_ISSUES" | jq -r '.[0].number') + echo "Updating existing issue #$ISSUE_NUMBER" + + curl -s -X PATCH -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + -H "Accept: application/vnd.github.v3+json" \ + -d "$ISSUE_DATA" \ + "https://api.github.com/repos/${{ github.repository }}/issues/$ISSUE_NUMBER" + else + # Create new issue + echo "Creating new DORA metrics issue" + + curl -s -X POST -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + -H "Accept: application/vnd.github.v3+json" \ + -d "$ISSUE_DATA" \ + "https://api.github.com/repos/${{ github.repository }}/issues" + fi + + - name: Commit badge files + run: | + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git add .github/badges/ + git commit -m "Update DORA metrics badges [skip ci]" || echo "No changes to commit" + git push \ No newline at end of file diff --git a/static/data/slim-registry.json b/static/data/slim-registry.json index 1bde98ebb..5ec83f258 100644 --- a/static/data/slim-registry.json +++ b/static/data/slim-registry.json @@ -378,10 +378,17 @@ "description": "Recommendations for metrics collection and an installation tool for Apache DevLake.", "tags": [ "software-lifecycle", - "metrics", + "metrics", "tools", "DORA", "devlake" + ], + "assets": [ + { + "name": "Simple Metrics Collection", + "type": "text/yaml", + "uri": "https://raw.githubusercontent.com/NASA-AMMOS/slim/main/static/assets/software-lifecycle/metrics/metrics.yml" + } ] }, {