Skip to content

Commit dc09506

Browse files
committed
chore: enhance CI/CD and development workflows
- Add comprehensive GitHub Actions workflows for CI, benchmarking, and custom actions - Update devcontainer configuration with additional extensions and settings - Introduce custom actions for parsing GNU time output, dumping runner stats, and setting up benchmarks - Update Makefile with more targets and improved dependency management - Add .editorconfig for consistent code formatting - Update rust-toolchain to latest version (1.84.1) - Improve dependency installation and environment setup scripts
1 parent 29fe58e commit dc09506

File tree

21 files changed

+1039
-208
lines changed

21 files changed

+1039
-208
lines changed

.devcontainer/devcontainer.json

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"configureZshAsDefaultShell": true,
1111
"installOhMyZsh": true,
1212
"installOhMyZshConfig": true,
13-
"upgradePackages": true,
13+
"upgradePackages": false,
1414
"username": "root"
1515
}
1616
},
@@ -31,21 +31,29 @@
3131
// "forwardPorts": [],
3232

3333
// Use 'postCreateCommand' to run commands after the container is created.
34-
"postCreateCommand": "rustc --version",
34+
// "postCreateCommand": "rustc --version",
3535

3636
// Configure tool-specific properties.
3737
"customizations": {
3838
"vscode": {
3939
"extensions": [
40-
"ms-vscode.makefile-tools",
4140
"ms-vscode-remote.remote-containers",
4241
"rust-lang.rust-analyzer",
4342
"eamodio.gitlens",
44-
"redhat.vscode-yaml"
43+
"redhat.vscode-yaml",
44+
"github.vscode-github-actions",
45+
"GitHub.vscode-codeql",
46+
"GitHub.codespaces",
47+
"GitHub.vscode-pull-request-github",
48+
"EditorConfig.EditorConfig"
4549
]
4650
}
4751
},
4852

4953
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
50-
"remoteUser": "root"
51-
}
54+
"remoteUser": "root",
55+
56+
"remoteEnv": {
57+
"GIT_EDITOR": "nano"
58+
}
59+
}

.editorconfig

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
6+
[Makefile*]
7+
indent_style = tab
8+
9+
[Dockerfile]
10+
indent_style = tab
11+
12+
[Dockerfile.**]
13+
indent_style = tab
14+
15+
[install-dependencies]
16+
indent_style = space
17+
indent_size = 2
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: 'Dump System Information'
2+
description: 'Dump system information about the runner'
3+
4+
inputs:
5+
cpuinfo_path:
6+
description: 'Path to CPU info file'
7+
required: false
8+
default: '/proc/cpuinfo'
9+
os_release_path:
10+
description: 'Path to OS release file'
11+
required: false
12+
default: '/etc/os-release'
13+
14+
runs:
15+
using: "composite"
16+
steps:
17+
- name: Dump runner stats
18+
shell: bash
19+
run: |
20+
echo "=== Runner System Information ==="
21+
echo "CPU Information:"
22+
echo " - Cores: $(nproc)"
23+
echo " - Model: $(grep "model name" ${{ inputs.cpuinfo_path }} | head -n1 | cut -d: -f2 | sed 's/^[ \t]*//')"
24+
echo " - Architecture: $(uname -m)"
25+
26+
echo -e "\nMemory Information:"
27+
echo " - Total RAM: $(free -h | awk '/^Mem:/{print $2}')"
28+
echo " - Available RAM: $(free -h | awk '/^Mem:/{print $7}')"
29+
echo " - Swap: $(free -h | awk '/^Swap:/{print $2}')"
30+
31+
echo -e "\nStorage Information:"
32+
echo " - Available space: $(df -h . | awk 'NR==2{print $4}')"
33+
echo " - Total space: $(df -h . | awk 'NR==2{print $2}')"
34+
echo " - Used space: $(df -h . | awk 'NR==2{print $3}')"
35+
echo " - Use percentage: $(df -h . | awk 'NR==2{print $5}')"
36+
37+
echo -e "\nOperating System:"
38+
echo " - $(cat ${{ inputs.os_release_path }} | grep PRETTY_NAME | cut -d'"' -f2)"
39+
echo " - Kernel: $(uname -r)"
40+
echo "==========================="
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Dependency directories
2+
node_modules/
3+
jspm_packages/
4+
5+
# Optional npm cache directory
6+
.npm
7+
8+
# Optional eslint cache
9+
.eslintcache
10+
11+
# Optional REPL history
12+
.node_repl_history
13+
14+
# Output of 'npm pack'
15+
*.tgz
16+
17+
# Logs
18+
logs
19+
*.log
20+
npm-debug.log*
21+
yarn-debug.log*
22+
yarn-error.log*
23+
24+
# Runtime data
25+
pids
26+
*.pid
27+
*.seed
28+
*.pid.lock
29+
30+
# Coverage directory used by tools like istanbul
31+
coverage/
32+
33+
# nyc test coverage
34+
.nyc_output/
35+
36+
# Compiled binary addons
37+
build/Release
38+
39+
# Dependency files
40+
package-lock.json
41+
yarn.lock
42+
43+
# Environment variables
44+
.env
45+
.env.local
46+
.env.*.local
47+
48+
# IDE specific files
49+
.idea/
50+
.vscode/
51+
*.swp
52+
*.swo
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# GNU Time Parser Action
2+
3+
A GitHub Action that parses the output of GNU's `/usr/bin/time -v` command and converts it into structured JSON data.
4+
5+
## Usage
6+
7+
Add this action to your workflow:
8+
9+
```yaml
10+
- uses: ./.github/actions/parse-gnu-time
11+
with:
12+
content: ${{ steps.bench.outputs.time_output }}
13+
```
14+
15+
## Development
16+
17+
### Install Dependencies
18+
```bash
19+
npm install
20+
```
21+
22+
### Run Tests
23+
```bash
24+
npm test
25+
```
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: 'Parse GNU Time Output'
2+
description: 'Parse and extract metrics from GNU time -v output'
3+
4+
inputs:
5+
content:
6+
description: 'The time command output content'
7+
required: true
8+
fields:
9+
description: 'Array of field names to include in output'
10+
required: false
11+
default: '["peak_memory_gb", "user_time_seconds", "system_time_seconds"]'
12+
13+
outputs:
14+
json:
15+
description: 'Parsed metrics in JSON format'
16+
value: ${{ steps.parse.outputs.json }}
17+
18+
runs:
19+
using: "composite"
20+
steps:
21+
- name: Install dependencies
22+
shell: bash
23+
run: cd ${{ github.action_path }} && npm ci --only=production
24+
25+
- name: Run parser
26+
id: parse
27+
shell: bash
28+
env:
29+
INPUT_CONTENT: ${{ inputs.content }}
30+
INPUT_FIELDS: ${{ inputs.fields }}
31+
run: node ${{ github.action_path }}/parse.js
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "parse-gnu-time",
3+
"version": "1.0.0",
4+
"description": "Parse GNU time -v output",
5+
"main": "parse.js",
6+
"scripts": {
7+
"test": "jest"
8+
},
9+
"dependencies": {
10+
"@actions/core": "^1.10.1"
11+
},
12+
"devDependencies": {
13+
"jest": "^26.6.3"
14+
}
15+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
function parseGnuTime(content, fields = []) {
2+
// Define mappings between time output and our metric names
3+
const TIME_METRICS = {
4+
'Command being timed': 'command',
5+
'User time (seconds)': 'user_time_seconds',
6+
'System time (seconds)': 'system_time_seconds',
7+
'Percent of CPU this job got': 'cpu_usage_percent',
8+
'Elapsed (wall clock) time (h:mm:ss or m:ss)': 'elapsed_time',
9+
'Maximum resident set size (kbytes)': 'peak_memory_kb',
10+
'Average shared text size (kbytes)': 'avg_shared_text_kb',
11+
'Average unshared data size (kbytes)': 'avg_unshared_data_kb',
12+
'Average stack size (kbytes)': 'avg_stack_kb',
13+
'Average total size (kbytes)': 'avg_total_kb',
14+
'Major (requiring I/O) page faults': 'major_page_faults',
15+
'Minor (reclaiming a frame) page faults': 'minor_page_faults',
16+
'Voluntary context switches': 'voluntary_ctx_switches',
17+
'Involuntary context switches': 'involuntary_ctx_switches',
18+
'Swaps': 'swaps',
19+
'File system inputs': 'fs_inputs',
20+
'File system outputs': 'fs_outputs',
21+
'Socket messages sent': 'socket_msgs_sent',
22+
'Socket messages received': 'socket_msgs_received',
23+
'Signals delivered': 'signals_delivered',
24+
'Page size (bytes)': 'page_size_bytes',
25+
'Exit status': 'exit_status'
26+
};
27+
28+
// Parse metrics
29+
const parsed = {};
30+
const lines = content.split('\n');
31+
for (const _line of lines) {
32+
const line = _line.trimStart().trimEnd();
33+
for (const [key, metric] of Object.entries(TIME_METRICS)) {
34+
const match = line.startsWith(key + ':') ? [line, line.slice(key.length + 1).trim()] : null;
35+
if (match) {
36+
let value = match[1].trim();
37+
38+
// Handle special cases
39+
if (metric === 'command') {
40+
value = value.replace(/^"|"$/g, ''); // Remove quotes
41+
} else if (metric === 'cpu_usage_percent') {
42+
value = parseFloat(value.replace('%', '')); // Remove % sign and convert to number
43+
} else if (metric === 'elapsed_time') {
44+
// Keep elapsed_time as string since it's a time format
45+
value = value;
46+
} else if (!isNaN(value)) {
47+
// Convert any numeric strings to numbers
48+
value = value.includes('.') ? parseFloat(value) : parseInt(value, 10);
49+
}
50+
51+
parsed[metric] = value;
52+
}
53+
}
54+
}
55+
56+
// Convert KB to GB for memory metrics
57+
for (const [key, value] of Object.entries(parsed)) {
58+
if (key.endsWith('_kb')) {
59+
const gbKey = key.replace('_kb', '_gb');
60+
parsed[gbKey] = parseFloat((value / 1048576).toFixed(2));
61+
}
62+
}
63+
64+
// Filter metrics if fields are specified
65+
const filtered = { command: parsed.command };
66+
if (fields.length > 0) {
67+
for (const field of fields) {
68+
if (parsed[field] !== undefined) {
69+
filtered[field] = parsed[field];
70+
}
71+
}
72+
} else {
73+
Object.assign(filtered, parsed);
74+
}
75+
76+
return {
77+
command: filtered.command,
78+
metrics: Object.fromEntries(
79+
Object.entries(filtered).filter(([key]) => key !== 'command')
80+
)
81+
};
82+
}
83+
84+
// When running as a script
85+
if (require.main === module) {
86+
const core = require('@actions/core');
87+
try {
88+
const content = core.getInput('content', { required: true });
89+
const fields = JSON.parse(core.getInput('fields') || '[]');
90+
91+
const result = parseGnuTime(content, fields);
92+
core.setOutput('json', JSON.stringify(result));
93+
} catch (error) {
94+
core.setFailed(error.message);
95+
}
96+
}
97+
98+
// Export for testing
99+
module.exports = parseGnuTime;

0 commit comments

Comments
 (0)