Skip to content

chore(metrics): usage metrics for modules#3737

Merged
mdelapenya merged 9 commits into
testcontainers:mainfrom
mdelapenya:feat/modules-usage-metrics
Jun 24, 2026
Merged

chore(metrics): usage metrics for modules#3737
mdelapenya merged 9 commits into
testcontainers:mainfrom
mdelapenya:feat/modules-usage-metrics

Conversation

@mdelapenya

@mdelapenya mdelapenya commented Jun 24, 2026

Copy link
Copy Markdown
Member

What does this PR do?

Adds a complete modules usage metrics system alongside the existing core library metrics:

  • collect.go: unified CLI replacing the two separate scripts (collect-metrics.go / collect-module-metrics.go); runs as collect versions or collect modules with a shared retry/CSV/sort engine
  • docs/usage-metrics/modules.csv: new CSV tracking monthly GitHub import counts per module (65 modules)
  • docs/usage-metrics/modules.md: interactive dashboard with stats cards, total/trend/latest charts, and a filterable per-module chart grid (one line chart per module, sorted alphabetically)
  • docs/usage-metrics/index.md: core library dashboard moved here so the "Usage Metrics" nav entry is a direct link
  • .github/workflows/usage-metrics-modules.yml: monthly workflow that auto-discovers all modules under modules/, queries GitHub Code Search, and opens a PR with the updated CSV
  • usage-metrics/collect_test.go: unit tests for all non-network logic (error classification, CSV append/sort, collect retry logic)
  • scripts/changed-modules.sh: updated excluded files list to include the new CSV paths and the new workflow

Why is it important?

Gives the project visibility into which individual modules are being adopted, not just the core library version. The per-module charts make it easy to spot trends, compare relative popularity, and track growth over time.

Related issues

@netlify

netlify Bot commented Jun 24, 2026

Copy link
Copy Markdown

Deploy Preview for testcontainers-go ready!

Name Link
🔨 Latest commit cb81c9b
🔍 Latest deploy log https://app.netlify.com/projects/testcontainers-go/deploys/6a3bcd1112c57c000827952b
😎 Deploy Preview https://deploy-preview-3737--testcontainers-go.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: be43edea-1f2f-4aad-9808-4a2e96528a97

📥 Commits

Reviewing files that changed from the base of the PR and between 3509b9b and cb81c9b.

📒 Files selected for processing (2)
  • usage-metrics/collect.go
  • usage-metrics/collect_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • usage-metrics/collect_test.go
  • usage-metrics/collect.go

Summary by CodeRabbit

  • New Features

    • Added a “Module Usage Metrics” dashboard with summary cards, multiple charts (including per-module trends), and a module filter.
    • Split usage metrics into separate core and modules experiences with updated navigation.
  • Bug Fixes

    • Improved automated metrics updates to avoid duplicate pull requests and commit only when generated CSV data changes.
    • Updated workflows to correctly source and preserve the latest metrics files during branch updates.
  • Documentation

    • Added a new modules tracking documentation page and refreshed the overall usage metrics guide, data formats, and workflow/manual trigger instructions.

Walkthrough

Adds a new modules usage-metrics workflow and dashboard, updates the core workflow and collector to use separate CSV outputs, and rewires MkDocs, helper scripts, and documentation for the split metrics structure.

Changes

Usage Metrics System Overhaul

Layer / File(s) Summary
Go collector and tests
usage-metrics/collect.go, usage-metrics/collect_test.go
Introduces collect.go with versions and modules subcommands, retry/sort/CSV helpers, and tests for error handling, CSV output, retries, and query strings.
Core metrics workflow path update
.github/workflows/usage-metrics.yml
Switches the core workflow to collect.go versions and updates all CSV references to docs/usage-metrics/core.csv, including branch reuse handling.
New modules metrics CI workflow
.github/workflows/usage-metrics-modules.yml
Adds a scheduled and manual workflow that discovers modules, runs collect.go modules, and creates or updates a branch and PR when modules.csv changes.
Modules dashboard UI and charts
docs/js/modules-usage-metrics.js, docs/css/usage-metrics.css, docs/usage-metrics/modules.md
Adds the modules dashboard page, its data loading and chart rendering script, filter UI behavior, and supporting styles for the per-module layout.
Site wiring and exclusions
mkdocs.yml, modulegen/internal/mkdocs/types.go, docs/js/usage-metrics.js, scripts/changed-modules.sh
Registers the new dashboard script, restructures Usage Metrics navigation, updates the config type, changes the core dashboard CSV fetch, and adjusts excluded files.
README update
usage-metrics/README.md
Rewrites the usage-metrics README for the split core/modules storage model, new workflows, updated commands, and refreshed operational notes.

Sequence Diagram(s)

sequenceDiagram
  participant Workflow as usage-metrics-modules.yml
  participant Collect as collect.go modules
  participant GitHubAPI as gh api /search/code
  participant CSV as docs/usage-metrics/modules.csv
  participant PR as branch + pull request

  Workflow->>Collect: go run collect.go modules -module ...
  loop per module and retry pass
    Collect->>GitHubAPI: search query
    GitHubAPI-->>Collect: total_count
    Collect->>CSV: appendToCSV
  end
  Collect->>CSV: sortCSV
  Workflow->>PR: create/update month-scoped branch
  Workflow->>PR: open PR to main if none exists
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

chore, documentation

🐇 I hopped through CSV fields with glee,
Two charts now bloom in the docs tree.
One branch for modules, one for core,
And PRs keep tidy what used to pour.
A carrot-bright dashboard now sings at dawn,
With filters and trends to nibble upon.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.95% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and clearly points to the new module usage metrics work.
Description check ✅ Passed The description matches the changeset and explains the new module metrics system.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mdelapenya mdelapenya force-pushed the feat/modules-usage-metrics branch from 6056710 to accd6e7 Compare June 24, 2026 10:47
Signed-off-by: mdelapenya <mdelapenya@gmail.com>
Signed-off-by: mdelapenya <mdelapenya@gmail.com>
Signed-off-by: mdelapenya <mdelapenya@gmail.com>
@mdelapenya mdelapenya closed this Jun 24, 2026
@mdelapenya mdelapenya deleted the feat/modules-usage-metrics branch June 24, 2026 12:17
@mdelapenya mdelapenya reopened this Jun 24, 2026
@mdelapenya mdelapenya marked this pull request as ready for review June 24, 2026 12:18
@mdelapenya mdelapenya requested a review from a team as a code owner June 24, 2026 12:18

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3509b9b1eb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +85 to +86
git fetch origin "$BRANCH_NAME"
git checkout "$BRANCH_NAME"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Fetch the existing metrics branch into a local ref

In the existing-branch path, this fetch does not make $BRANCH_NAME checkoutable in the default actions/checkout clone: I checked git fetch -h, where the branch argument is just a <refspec> and fetched refs are written to FETCH_HEAD unless a destination ref is provided. When a modules metrics PR already exists for the month, the following git checkout "$BRANCH_NAME" can fail with pathspec ... did not match, so reruns cannot update the open PR; fetch into refs/remotes/origin/$BRANCH_NAME or check out FETCH_HEAD with -B.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/usage-metrics-modules.yml:
- Around line 37-60: Inline interpolation of github.event.inputs.modules in the
workflow shell block can be interpreted as shell code, so move the modules input
into an env variable and read it from the script instead. Update the
usage-metrics-modules workflow’s run step to reference a safe variable such as
MODULES_INPUT, then assign MODULES from that env var before the existing
MODULE_FLAGS parsing logic in the shell block. Keep the existing module parsing
and go run collect.go invocation unchanged apart from sourcing the input from
env.

In `@usage-metrics/collect.go`:
- Around line 182-186: The CSV write loop in collect.go is currently swallowing
appendToCSV failures by logging a warning and continuing, which can leave
partial or stale output while still reporting success. Update the
results-processing path around appendToCSV so that a write error is treated as
fatal: stop processing immediately and propagate the error back to the caller
instead of continuing the loop. Use the existing appendToCSV and results
iteration in the collection flow to locate the change, and ensure the
surrounding function returns the failure so the overall operation fails fast.
- Around line 262-267: The sort comparator in sort.SliceStable on data assumes
every row has at least two columns, which can panic on malformed input. Add a
pre-sort validation step in the collect.go flow to check each row’s width before
accessing data[i][0] and data[i][1], and handle invalid rows by skipping them or
returning an error; use the data slice and the sort.SliceStable comparator as
the key locations to update.
- Around line 216-220: The gh api invocation in collect.go can hang indefinitely
because exec.Command is used without a deadline. Update the external call in the
collection flow to use a context with timeout (around the gh api call that
produces output), and thread that context through the command creation so hung
requests are canceled; keep the fix localized around the gh api execution path
used by the collection logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 545d1962-8133-4daa-b15d-b3614335f54c

📥 Commits

Reviewing files that changed from the base of the PR and between 478d46b and 3509b9b.

⛔ Files ignored due to path filters (2)
  • docs/usage-metrics/core.csv is excluded by !**/*.csv
  • docs/usage-metrics/modules.csv is excluded by !**/*.csv
📒 Files selected for processing (14)
  • .github/workflows/usage-metrics-modules.yml
  • .github/workflows/usage-metrics.yml
  • docs/css/usage-metrics.css
  • docs/js/modules-usage-metrics.js
  • docs/js/usage-metrics.js
  • docs/usage-metrics/index.md
  • docs/usage-metrics/modules.md
  • mkdocs.yml
  • modulegen/internal/mkdocs/types.go
  • scripts/changed-modules.sh
  • usage-metrics/README.md
  • usage-metrics/collect-metrics.go
  • usage-metrics/collect.go
  • usage-metrics/collect_test.go
💤 Files with no reviewable changes (1)
  • usage-metrics/collect-metrics.go

Comment on lines +37 to +60
run: |
# Build module flags array
MODULE_FLAGS=()
MODULES="${{ github.event.inputs.modules }}"
if [ -z "$MODULES" ]; then
# Discover all modules by listing subdirectories under modules/ that contain a go.mod file
while IFS= read -r go_mod_path; do
module="${go_mod_path#../modules/}"
module="${module%/go.mod}"
[ -n "$module" ] && MODULE_FLAGS+=("-module" "$module")
done < <(find ../modules -mindepth 2 -maxdepth 2 -name go.mod | sort)
else
IFS=',' read -ra MODULE_ARRAY <<< "$MODULES"
for module in "${MODULE_ARRAY[@]}"; do
module="${module#"${module%%[![:space:]]*}"}" # trim leading whitespace
module="${module%"${module##*[![:space:]]}"}" # trim trailing whitespace
[ -n "$module" ] && MODULE_FLAGS+=("-module" "$module")
done
fi

echo "Querying modules: ${MODULE_FLAGS[*]}"

# Query all modules in a single command
go run collect.go modules "${MODULE_FLAGS[@]}" -csv "../docs/usage-metrics/modules.csv"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Avoid template injection: pass inputs.modules via env, not inline interpolation.

${{ github.event.inputs.modules }} is expanded directly into the shell script body at Line 40, so a crafted workflow_dispatch input is interpreted as shell code rather than data. Bind it to an env var and reference it with $MODULES_INPUT instead.

🔒 Proposed fix
       - name: Query modules
         id: query
         working-directory: usage-metrics
+        env:
+          MODULES_INPUT: ${{ github.event.inputs.modules }}
         run: |
           # Build module flags array
           MODULE_FLAGS=()
-          MODULES="${{ github.event.inputs.modules }}"
+          MODULES="$MODULES_INPUT"
🧰 Tools
🪛 zizmor (1.26.1)

[error] 40-40: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/usage-metrics-modules.yml around lines 37 - 60, Inline
interpolation of github.event.inputs.modules in the workflow shell block can be
interpreted as shell code, so move the modules input into an env variable and
read it from the script instead. Update the usage-metrics-modules workflow’s run
step to reference a safe variable such as MODULES_INPUT, then assign MODULES
from that env var before the existing MODULE_FLAGS parsing logic in the shell
block. Keep the existing module parsing and go run collect.go invocation
unchanged apart from sourcing the input from env.

Source: Linters/SAST tools

Comment thread usage-metrics/collect.go
Comment on lines +182 to +186
for item, count := range results {
if err := appendToCSV(csvPath, column, date, item, count); err != nil {
log.Printf("Warning: failed to write %s=%s: %v", column, item, err)
continue
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Fail fast on CSV write errors instead of silently continuing.

Line 182-Line 186 currently logs write failures and continues, which can produce partial/stale datasets while returning success.

Suggested fix
 	for item, count := range results {
 		if err := appendToCSV(csvPath, column, date, item, count); err != nil {
-			log.Printf("Warning: failed to write %s=%s: %v", column, item, err)
-			continue
+			return fmt.Errorf("write %s=%s: %w", column, item, err)
 		}
 		fmt.Printf("Successfully recorded: %s=%s has %d usages on %s\n", column, item, count, date)
 	}

As per coding guidelines, data write failures that can silently corrupt downstream outputs should be treated as major integrity issues.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for item, count := range results {
if err := appendToCSV(csvPath, column, date, item, count); err != nil {
log.Printf("Warning: failed to write %s=%s: %v", column, item, err)
continue
}
for item, count := range results {
if err := appendToCSV(csvPath, column, date, item, count); err != nil {
return fmt.Errorf("write %s=%s: %w", column, item, err)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@usage-metrics/collect.go` around lines 182 - 186, The CSV write loop in
collect.go is currently swallowing appendToCSV failures by logging a warning and
continuing, which can leave partial or stale output while still reporting
success. Update the results-processing path around appendToCSV so that a write
error is treated as fatal: stop processing immediately and propagate the error
back to the caller instead of continuing the loop. Use the existing appendToCSV
and results iteration in the collection flow to locate the change, and ensure
the surrounding function returns the failure so the overall operation fails
fast.

Source: Coding guidelines

Comment thread usage-metrics/collect.go
Comment on lines +216 to +220
output, err := exec.Command("gh", "api",
"-H", "Accept: application/vnd.github+json",
"-H", "X-GitHub-Api-Version: 2022-11-28",
endpoint,
).Output()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Add a timeout to gh api calls to avoid hung collection runs.

Line 216-Line 220 runs an external process without a deadline; if it hangs, the job can stall until workflow timeout.

Suggested fix
 import (
+	"context"
 	"encoding/csv"
 	"encoding/json"
 	"errors"
@@
 func runGHSearch(query string) (int, error) {
 	params := url.Values{}
 	params.Add("q", query)
 	endpoint := "/search/code?" + params.Encode()
 
-	output, err := exec.Command("gh", "api",
+	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+	defer cancel()
+
+	output, err := exec.CommandContext(ctx, "gh", "api",
 		"-H", "Accept: application/vnd.github+json",
 		"-H", "X-GitHub-Api-Version: 2022-11-28",
 		endpoint,
 	).Output()
 	if err != nil {
+		if errors.Is(ctx.Err(), context.DeadlineExceeded) {
+			return 0, fmt.Errorf("gh api timeout: %w", ctx.Err())
+		}
 		exitErr := &exec.ExitError{}
 		if errors.As(err, &exitErr) {
 			return 0, fmt.Errorf("gh api failed: %s", string(exitErr.Stderr))
 		}

As per coding guidelines, blocking external calls without timeouts are release-blocking reliability hazards.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
output, err := exec.Command("gh", "api",
"-H", "Accept: application/vnd.github+json",
"-H", "X-GitHub-Api-Version: 2022-11-28",
endpoint,
).Output()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
output, err := exec.CommandContext(ctx, "gh", "api",
"-H", "Accept: application/vnd.github+json",
"-H", "X-GitHub-Api-Version: 2022-11-28",
endpoint,
).Output()
if err != nil {
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
return 0, fmt.Errorf("gh api timeout: %w", ctx.Err())
}
exitErr := &exec.ExitError{}
if errors.As(err, &exitErr) {
return 0, fmt.Errorf("gh api failed: %s", string(exitErr.Stderr))
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@usage-metrics/collect.go` around lines 216 - 220, The gh api invocation in
collect.go can hang indefinitely because exec.Command is used without a
deadline. Update the external call in the collection flow to use a context with
timeout (around the gh api call that produces output), and thread that context
through the command creation so hung requests are canceled; keep the fix
localized around the gh api execution path used by the collection logic.

Source: Coding guidelines

Comment thread usage-metrics/collect.go
Comment on lines +262 to +267
sort.SliceStable(data, func(i, j int) bool {
if data[i][0] != data[j][0] {
return data[i][0] < data[j][0]
}
return data[i][1] < data[j][1]
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Validate row width before sorting to prevent index panics.

Line 262-Line 267 indexes column 0/1 directly; malformed CSV rows can cause runtime panics.

Suggested fix
 	header := records[0]
 	data := records[1:]
+	for i, row := range data {
+		if len(row) < 2 {
+			return fmt.Errorf("invalid csv row %d: expected at least 2 columns, got %d", i+2, len(row))
+		}
+	}
 
 	sort.SliceStable(data, func(i, j int) bool {
 		if data[i][0] != data[j][0] {
 			return data[i][0] < data[j][0]

As per coding guidelines, missing boundary checks that can trigger out-of-bounds runtime failures are critical runtime pitfalls to address.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
sort.SliceStable(data, func(i, j int) bool {
if data[i][0] != data[j][0] {
return data[i][0] < data[j][0]
}
return data[i][1] < data[j][1]
})
for i, row := range data {
if len(row) < 2 {
return fmt.Errorf("invalid csv row %d: expected at least 2 columns, got %d", i+2, len(row))
}
}
sort.SliceStable(data, func(i, j int) bool {
if data[i][0] != data[j][0] {
return data[i][0] < data[j][0]
}
return data[i][1] < data[j][1]
})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@usage-metrics/collect.go` around lines 262 - 267, The sort comparator in
sort.SliceStable on data assumes every row has at least two columns, which can
panic on malformed input. Add a pre-sort validation step in the collect.go flow
to check each row’s width before accessing data[i][0] and data[i][1], and handle
invalid rows by skipping them or returning an error; use the data slice and the
sort.SliceStable comparator as the key locations to update.

Source: Coding guidelines

Signed-off-by: mdelapenya <mdelapenya@gmail.com>
@mdelapenya mdelapenya self-assigned this Jun 24, 2026
@mdelapenya mdelapenya added the documentation Docs, docs, docs. label Jun 24, 2026
@mdelapenya mdelapenya merged commit 9f30d4a into testcontainers:main Jun 24, 2026
223 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Docs, docs, docs.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant