Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 21 additions & 16 deletions scripts/automation/OPTIMIZATION-GUIDE.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
---
title: "Automation Scripts Performance Optimization Guide"
description: "Phase 2 optimization strategy with baseline metrics, tiered approaches, and implementation roadmap"
status: active
version: "1.0"
created_date: "2026-08-30"
last_updated: "2026-08-30"
title: Automation Scripts Performance Optimization Guide
description: Phase 2 optimization strategy targeting 30% performance improvement
version: 1.0
status: Phase 2 Implementation Plan
updated: 2026-08-30
---

# Automation Scripts Performance Optimization Guide
Expand Down Expand Up @@ -258,20 +257,22 @@ async function validateIssues(issues) {
async function validateIssuesWithEarlyExit(issues, errorThreshold = 0.1) {
const results = [];
const errors = [];
let earlyExit = false;

for (const issue of issues) {
const result = validate(issue);
if (result.error) {
errors.push(result);
// Exit early if error rate exceeds threshold
if (errors.length / (results.length + 1) > errorThreshold) {
earlyExit = true;
break;
}
}
results.push(result);
}

return { results, errors, earlyExit: errors.length > 0 };
return { results, errors, earlyExit };
}
```

Expand Down Expand Up @@ -466,20 +467,19 @@ async function auditAllIssues(numbers) {
```javascript
import { batchFetchIssues } from './includes/github-api-optimized.js';

// Usage: Parallel batch fetching with caching
// Usage: Concurrent fetching with caching
async function auditAllIssues(numbers) {
return batchFetchIssues(OWNER, REPO, numbers, {
concurrency: 5,
perPage: 30
concurrency: 5
});
}
```

**Performance Impact:**

- Sequential: 145 issues × 100ms = 14.5 seconds
- Optimized (batch of 5): 29 batches × 100ms = 2.9 seconds
- **Improvement: 80% faster**
- Optimized (concurrent, 5 at a time): 29 groups × 100ms = 2.9 seconds
- **Improvement: 80% faster** (due to concurrency, not API call reduction)

---

Expand All @@ -502,15 +502,16 @@ async function getRepoLabels(owner, repo) {
`/repos/${owner}/${repo}/labels`
);

// Write operations clear cache
// Write operations: POST without cache, then clear cache
await githubApiRequest(
'POST',
`/repos/${owner}/${repo}/labels`,
{ name: 'new-label', color: '0366d6' },
{ useCache: false }
);
clearCache();

// Next read fetches fresh data
// Next read fetches fresh data from API
const labels3 = await githubApiRequest(
'GET',
`/repos/${owner}/${repo}/labels`
Expand All @@ -527,7 +528,7 @@ async function getRepoLabels(owner, repo) {
- [ ] Run profiler before optimization: `npm run profile:baseline`
- [ ] Run profiler after optimization: `npm run profile:optimized`
- [ ] Compare execution times (target: ≥20% improvement)
- [ ] Verify memory usage stays below 8 MB
- [ ] Verify memory usage stays below 7.5 MB
- [ ] Validate no functional regressions
- [ ] Test with dry-run mode: `--dry-run` flag
- [ ] Test with sample issue sets: 10, 38, 100 issues
Expand Down Expand Up @@ -577,7 +578,7 @@ npm run triage:analyze -- --issue 1 2352 2146 2442 2396
| Metric | Current | Target | Pass Criteria |
|--------|---------|--------|--------------|
| Total Execution Time | 13.3s | ≤9.3s | ✓ 30% improvement |
| Peak Memory Usage | 10.44 MB | ≤8 MB | ✓ 23% reduction |
| Peak Memory Usage | 10.44 MB | ≤7.5 MB | ✓ 28% reduction |
| API Calls per Run | ~145 | ≤110 | ✓ 24% reduction |
| Network Round-trips | ~145 | ≤85 | ✓ 41% reduction |
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

Expand All @@ -604,6 +605,10 @@ npm run triage:analyze -- --issue 1 2352 2146 2442 2396
## Related Documentation

- [github-api-optimized.js](./includes/github-api-optimized.js) — Optimized API client module
<<<<<<< HEAD
=======
- [profiler.js](./profiler.js) — Performance profiling utility
>>>>>>> 2991e2b58 (fix: Address all CodeRabbit review findings for Phase 2B optimization)
- [AUTOMATION_GOVERNANCE.md](../../docs/AUTOMATION_GOVERNANCE.md) — Governance rules
- [AGENTS.md](../../AGENTS.md) — AI operations guidelines

Expand Down
192 changes: 62 additions & 130 deletions scripts/automation/audit-issue-metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@
* --verbose Show detailed progress
*/

import https from "https";
import {
getCacheStats,
fetchPaginated,
} from "./includes/github-api-optimized.js";
import fs from "fs";
import path from "path";

Expand Down Expand Up @@ -75,84 +78,23 @@ const statusLabelsToAudit = [
"status:needs-dev",
];

// Utility: Make GitHub API request
async function githubRequest(method, path, body = null) {
return new Promise((resolve, reject) => {
const options = {
hostname: "api.github.com",
path,
method,
headers: {
Authorization: `token ${token}`,
"User-Agent": "Audit-Issue-Metadata",
Accept: "application/vnd.github.v3+json",
"Content-Type": "application/json",
},
};

const req = https.request(options, (res) => {
let data = "";

res.on("data", (chunk) => {
data += chunk;
});

res.on("end", () => {
try {
const json = JSON.parse(data);
if (res.statusCode >= 400) {
reject(
new Error(
`GitHub API error ${res.statusCode}: ${json.message || data}`,
),
);
} else {
resolve({ status: res.statusCode, data: json });
}
} catch (e) {
reject(e);
}
});
});

req.on("error", reject);

if (body) {
req.write(JSON.stringify(body));
}

req.end();
});
}

// Parse labels into categories (optimized with prefix detection - Phase 2)
// Parse labels into categories
function categorizeLabels(labels) {
const result = { type: [], area: [], status: [], priority: [], other: [] };

for (const label of labels) {
const name = label.name || label;
const colonIdx = name.indexOf(":");

// Fast categorization using prefix detection
if (colonIdx > 0) {
const prefix = name.substring(0, colonIdx + 1);
switch (prefix) {
case "type:":
result.type.push(name);
continue;
case "area:":
result.area.push(name);
continue;
case "status:":
result.status.push(name);
continue;
case "priority:":
result.priority.push(name);
continue;
}
if (name.startsWith("type:")) {
result.type.push(name);
} else if (name.startsWith("area:")) {
result.area.push(name);
} else if (name.startsWith("status:")) {
result.status.push(name);
} else if (name.startsWith("priority:")) {
result.priority.push(name);
} else {
result.other.push(name);
}

result.other.push(name);
}

return result;
Expand Down Expand Up @@ -195,61 +137,43 @@ function analyzeIssue(issue) {
};
}

// Fetch all open issues with pagination
// Fetch all open issues with pagination (optimized)
async function fetchAllIssues() {
const allIssues = [];
let page = 1;
let hasMore = true;

console.log("📥 Fetching all open issues...");

while (hasMore && allIssues.length < config.limit) {
// Build path using labels parameter if filter is specified
let path = `/repos/${config.owner}/${config.repo}/issues?state=open&per_page=${config.perPage}&page=${page}&sort=created&order=asc`;
if (config.filter) {
path += `&labels=${encodeURIComponent(config.filter)}`;
}
// Build path using labels parameter if filter is specified
let path = `/repos/${config.owner}/${config.repo}/issues?state=open&sort=created&order=asc`;
if (config.filter) {
path += `&labels=${encodeURIComponent(config.filter)}`;
}

try {
const response = await githubRequest("GET", path);
const rawPage = response.data;
const pageSize = rawPage?.length || 0;

if (pageSize === 0) {
hasMore = false;
} else {
// Filter out pull requests (API returns both issues and PRs)
const issues = rawPage.filter((item) => !item.pull_request);
allIssues.push(...issues);

if (config.verbose) {
console.log(
` ✓ Fetched page ${page} (${issues.length} issues, total: ${allIssues.length})`,
);
}
try {
// Use optimized paginated fetch with caching
const allResults = await fetchPaginated(path, {
token: process.env.GITHUB_TOKEN,
headers: {
"User-Agent": "Audit-Issue-Metadata",
Accept: "application/vnd.github.v3+json",
},
useCache: true,
perPage: config.perPage,
});

// Use raw page size to determine if more pages exist
if (pageSize < config.perPage) {
hasMore = false;
} else {
page++;
}
}
} catch (error) {
console.error(`Error: Failed to fetch page ${page}: ${error.message}`);
console.error(
"Aborting audit due to fetch failure (partial data is unreliable)",
);
process.exit(1);
}
// Filter out pull requests (API returns both issues and PRs)
const allIssues = allResults.filter((item) => !item.pull_request);

// Rate limit protection: sleep briefly between requests
if (hasMore) {
await new Promise((resolve) => setTimeout(resolve, 100));
if (config.verbose) {
console.log(` ✓ Fetched ${allIssues.length} total issues`);
}
}

return allIssues.slice(0, config.limit);
return allIssues.slice(0, config.limit);
} catch (error) {
console.error(`Error: Failed to fetch issues: ${error.message}`);
console.error(
"Aborting audit due to fetch failure (partial data is unreliable)",
);
process.exit(1);
}
}

// Generate comprehensive audit report
Expand Down Expand Up @@ -355,18 +279,15 @@ function exportJSON(data) {
return filename;
}

// Export as CSV (optimized for large datasets - Phase 2)
// Export as CSV
function exportCSV(data) {
const filename = path.join(config.outputDir, "audit-results.csv");

// Pre-allocate rows array to avoid reallocation
const rows = new Array(data.analyzedIssues.length + 1);
rows[0] =
"Issue #,Title,Type Labels,Area Labels,Status Labels,Priority Labels,Assignees,Milestone,Gaps";
const rows = [
"Issue #,Title,Type Labels,Area Labels,Status Labels,Priority Labels,Assignees,Milestone,Gaps",
];

// Batch process issues for better performance
for (let i = 0; i < data.analyzedIssues.length; i++) {
const issue = data.analyzedIssues[i];
data.analyzedIssues.forEach((issue) => {
const row = [
issue.number,
`"${issue.title.replace(/"/g, '""')}"`,
Expand All @@ -378,8 +299,8 @@ function exportCSV(data) {
issue.milestone || "",
issue.gaps.join(";"),
];
rows[i + 1] = row.join(",");
}
rows.push(row.join(","));
});

fs.writeFileSync(filename, rows.join("\n"));
console.log(`✅ CSV export: ${filename}`);
Expand Down Expand Up @@ -558,6 +479,17 @@ async function main() {
console.log(
`\n Top Gap: ${reportData.stats.topGaps[0]?.gap} (${reportData.stats.topGaps[0]?.count} issues)`,
);

// Cache statistics
const cacheStats = getCacheStats();
console.log("\n⚡ Performance Metrics:");
console.log(
` Cache Entries: ${cacheStats.validEntries} (${cacheStats.expiredEntries} expired)`,
);
console.log(
` Cache Size: ${(cacheStats.cacheSizeBytes / 1024).toFixed(2)} KB`,
);

console.log(`\n✅ Audit complete. Reports saved to: ${config.outputDir}`);
}

Expand Down
Loading
Loading