From 32054d035dc4f4b2a5251ef5664ef82f71787cf0 Mon Sep 17 00:00:00 2001 From: Luis Chamberlain Date: Mon, 22 Sep 2025 09:35:31 -0700 Subject: [PATCH] contrib_graph: track Generated-by adoption trend Add git log parsing for the Generated-by trailer and render a monthly adoption timeline in the dashboard. Rework the layout to fit the new subplot and show tag totals in the stats panel. Update the contribution docs to describe the seventh panel. Generated-by: ChatGPT Codex Signed-off-by: Luis Chamberlain --- docs/contrib/README.md | 9 +- scripts/contrib_graph.py | 173 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 168 insertions(+), 14 deletions(-) diff --git a/docs/contrib/README.md b/docs/contrib/README.md index 905759f45..ec5b28469 100644 --- a/docs/contrib/README.md +++ b/docs/contrib/README.md @@ -25,7 +25,7 @@ make contrib-graph ## Generated Files -The visualization tool creates comprehensive 6-panel dashboards saved as: +The visualization tool creates comprehensive 7-panel dashboards saved as: ### For Specific Year - `kdevops_contributions_YYYY.png` - High-resolution image (300 DPI) @@ -37,7 +37,7 @@ The visualization tool creates comprehensive 6-panel dashboards saved as: ## Visualization Components -Each generated dashboard includes six analytical panels: +Each generated dashboard includes seven analytical panels: ### 1. Total Commits Bar Chart Shows the overall contribution ranking by commit count. Names are automatically shortened for contributors with fewer commits to prevent overlapping labels. @@ -61,6 +61,10 @@ Key metrics including: - Most active month - Top contributor - Analysis period and generation timestamp +- Generated-by tag usage share + +### 7. Generated-by Tag Adoption Trend +Shows how often commits include the `Generated-by:` trailer each month. This timeline highlights adoption of automated contribution tags and makes it easy to spot growth in AI-assisted commit generation. ## Technical Details @@ -141,6 +145,7 @@ The contribution analysis is integrated into the kdevops build system: - **Growing Contributor Base**: More contributors over time - **Sustained Activity**: Regular commits throughout periods - **Balanced Contributions**: Activity from multiple contributors +- **Generated-by Adoption**: Visibility into automated tag usage trends ## Files in This Directory diff --git a/scripts/contrib_graph.py b/scripts/contrib_graph.py index 4a9556a70..5d0d7bc31 100755 --- a/scripts/contrib_graph.py +++ b/scripts/contrib_graph.py @@ -13,6 +13,7 @@ import calendar from collections import defaultdict import matplotlib.pyplot as plt +import matplotlib.dates as mdates import numpy as np import pandas as pd import seaborn as sns @@ -143,6 +144,24 @@ def get_contribution_data(year=None, month=None): except (ValueError, IndexError): continue + # Track Generated-by usage over time + cmd = ( + f'git log {date_filter} --grep="Generated-by:" ' + '--pretty=format:"%cd" --date=format:"%Y-%m"' + ) + output = run_git_command(cmd) + + generated_by_monthly = defaultdict(int) + + for line in output.split("\n"): + entry = line.strip() + if entry: + try: + month_date = datetime.strptime(f"{entry}-01", "%Y-%m-%d").date() + generated_by_monthly[month_date] += 1 + except ValueError: + continue + # Get total commits for period calculation cmd = f"git log {date_filter} --oneline | wc -l" total_commits = int(run_git_command(cmd)) @@ -170,6 +189,7 @@ def get_contribution_data(year=None, month=None): total_commits, period_desc, ai_boom_marker, + dict(generated_by_monthly), ) @@ -179,11 +199,15 @@ def create_contribution_graphs( total_commits, period_desc, ai_boom_marker=None, + generated_by_monthly=None, year=None, month=None, ): """Create comprehensive visualization graphs""" + if generated_by_monthly is None: + generated_by_monthly = {} + # Determine output filename if year: if month: @@ -222,12 +246,19 @@ def create_contribution_graphs( title_suffix = " (All Time)" # Create figure with multiple subplots and better styling - fig = plt.figure(figsize=(22, 14), facecolor='#f8f9fa') + fig = plt.figure(figsize=(24, 16), facecolor='#f8f9fa') + gs = fig.add_gridspec( + 3, + 3, + height_ratios=[1, 1, 1.1], + hspace=0.35, + wspace=0.3, + ) fig.suptitle( f"kdevops Contribution Analysis{title_suffix}", fontsize=24, fontweight="bold", - y=0.98, + y=0.985, color='#2c3e50' ) @@ -236,7 +267,7 @@ def create_contribution_graphs( return None # 1. Total Contributions Bar Chart - ax1 = plt.subplot(2, 3, 1) + ax1 = fig.add_subplot(gs[0, 0]) contributors = list(contributors_total.keys()) commits = list(contributors_total.values()) @@ -296,7 +327,7 @@ def create_contribution_graphs( ) # 2. Pie Chart of Contributions (top contributors only) - ax2 = plt.subplot(2, 3, 2) + ax2 = fig.add_subplot(gs[0, 1]) # Group smaller contributors together main_contributors = dict(list(contributors_total.items())[:8]) # Top 8 others_count = ( @@ -344,7 +375,7 @@ def create_contribution_graphs( ) # 3. Monthly Activity Heatmap - ax3 = plt.subplot(2, 3, 3) + ax3 = fig.add_subplot(gs[0, 2]) # Get months that have activity active_months = set() @@ -427,7 +458,7 @@ def create_contribution_graphs( ax3.set_title("Monthly Activity Heatmap", fontweight="bold", fontsize=14) # 4. Monthly Timeline - ax4 = plt.subplot(2, 3, 4) + ax4 = fig.add_subplot(gs[1, 0]) if active_months: # Calculate total commits per month @@ -562,7 +593,7 @@ def create_contribution_graphs( ax4.set_title("Monthly Commit Activity", fontweight="bold", fontsize=14) # 5. Top Contributors Timeline - ax5 = plt.subplot(2, 3, 5) + ax5 = fig.add_subplot(gs[1, 1]) if active_months: @@ -720,7 +751,7 @@ def create_contribution_graphs( ) # 6. Statistics Summary - ax6 = plt.subplot(2, 3, 6) + ax6 = fig.add_subplot(gs[1, 2]) ax6.axis("off") # Calculate stats @@ -774,6 +805,11 @@ def create_contribution_graphs( else ("N/A", 0) ) + generated_by_total = sum(generated_by_monthly.values()) + generated_by_percentage = ( + (generated_by_total / total_commits) * 100 if total_commits else 0 + ) + # Add note about date anomaly if we detect future dates current_year = datetime.now().year date_note = "" @@ -794,6 +830,8 @@ def create_contribution_graphs( Active Months: {active_months_count} +Generated-by Tag Commits: {generated_by_total} ({generated_by_percentage:.1f}% of total) + Project: Linux Kernel DevOps Framework Analysis Period: {period_desc} Generated: {datetime.now().strftime('%Y-%m-%d %H:%M')}{date_note} @@ -816,7 +854,112 @@ def create_contribution_graphs( color='#2c3e50' ) - plt.tight_layout(pad=2.0) + # 7. Generated-by Tag Adoption Trend + ax7 = fig.add_subplot(gs[2, :]) + if generated_by_monthly: + timeline_map = {k: v for k, v in generated_by_monthly.items()} + sorted_dates = sorted(timeline_map.keys()) + min_date = sorted_dates[0] + max_date = sorted_dates[-1] + date_range = pd.date_range(start=min_date, end=max_date, freq="MS") + timeline_dates = [d.to_pydatetime() for d in date_range] + timeline_counts = [timeline_map.get(d.date(), 0) for d in date_range] + + ax7.plot( + timeline_dates, + timeline_counts, + marker="o", + linewidth=3, + markersize=8, + color="#8e44ad", + markerfacecolor="#f39c12", + markeredgecolor="#2c3e50", + markeredgewidth=1.2, + ) + if len(timeline_dates) > 1: + ax7.fill_between( + timeline_dates, + timeline_counts, + alpha=0.15, + color="#8e44ad", + ) + + locator = mdates.AutoDateLocator() + formatter = mdates.ConciseDateFormatter(locator) + ax7.xaxis.set_major_locator(locator) + ax7.xaxis.set_major_formatter(formatter) + + ax7.set_title( + "Generated-by Tag Adoption Over Time", + fontweight="bold", + fontsize=16, + color='#2c3e50', + pad=20, + ) + ax7.set_ylabel("Commits with Generated-by", fontsize=12, color='#34495e') + ax7.set_xlabel("Month", fontsize=12, color='#34495e') + ax7.spines['top'].set_visible(False) + ax7.spines['right'].set_visible(False) + ax7.grid(True, alpha=0.3) + ax7.set_ylim(bottom=0) + ax7.tick_params(axis="x", labelrotation=45) + + ax7.text( + 0.01, + 0.95, + f"Total commits tagged: {generated_by_total} ({generated_by_percentage:.1f}% of commits)", + transform=ax7.transAxes, + fontsize=10, + fontweight="bold", + color="#2c3e50", + bbox=dict( + boxstyle="round,pad=0.4", + facecolor="#f4ecf7", + edgecolor="#8e44ad", + linewidth=1.2, + alpha=0.6, + ), + verticalalignment="top", + ) + + if (year is None) or (int(year) == datetime.now().year): + current_marker = datetime.now() + if timeline_dates and timeline_dates[0] <= current_marker <= timeline_dates[-1]: + ax7.axvline( + current_marker, + color="#7f8c8d", + linestyle=":", + linewidth=1.2, + alpha=0.7, + ) + else: + ax7.set_title( + "Generated-by Tag Adoption Over Time", + fontweight="bold", + fontsize=16, + color="#2c3e50", + pad=20, + ) + ax7.spines['top'].set_visible(False) + ax7.spines['right'].set_visible(False) + ax7.set_ylabel("Commits with Generated-by", fontsize=12, color="#34495e") + ax7.set_xlabel("Month", fontsize=12, color="#34495e") + ax7.tick_params(axis="x", labelbottom=False) + ax7.tick_params(axis="y", labelleft=False) + ax7.set_ylim(0, 1) + ax7.text( + 0.5, + 0.5, + "No Generated-by tag usage detected", + ha="center", + va="center", + transform=ax7.transAxes, + fontsize=12, + fontweight="bold", + color="#7f8c8d", + ) + + fig.tight_layout(rect=[0, 0, 1, 0.97], pad=2.5) # Ensure output directory exists and save the plots os.makedirs("docs/contrib", exist_ok=True) @@ -897,9 +1040,14 @@ def main(): # Get contribution data month_int = int(args.month) if args.month else None - contributors_total, monthly_data, total_commits, period_desc, ai_boom_marker = ( - get_contribution_data(args.year, month_int) - ) + ( + contributors_total, + monthly_data, + total_commits, + period_desc, + ai_boom_marker, + generated_by_monthly, + ) = get_contribution_data(args.year, month_int) # Create visualization fig = create_contribution_graphs( @@ -908,6 +1056,7 @@ def main(): total_commits, period_desc, ai_boom_marker, + generated_by_monthly, args.year, month_int, )