Parse and Upload Actions¶
Files:
.github/actions/parse/action.yml- Parse benchmark logs.github/actions/upload/action.yml- Upload to LogStash/Kibana
These custom composite actions handle parsing benchmark logs and uploading results to Elasticsearch/Kibana for visualization and analysis.
Purpose¶
After each benchmark job completes, these actions:
- Parse action: Parses the log file to extract timing and performance metrics, generating a JSON payload
- Upload action: Sends the JSON payload to LogStash for routing and storage in Elasticsearch/Kibana
Architecture¶
The parsing and upload workflow uses two separate actions to process and send benchmark data:
Key architectural details:
- LogStash endpoint: LogStash routes requests to the appropriate Kibana instance based on the
token+kindcombination - Authentication/Routing: The
tokenandkindfields are sent as part of the JSON document body (NOT as Bearer authentication in HTTP headers) - No traditional credentials: This design eliminates the need for username/password authentication - routing is handled via the token+kind fields in the data payload
This routing approach allows different benchmark types and projects to be automatically directed to their respective Kibana instances while maintaining a single, simple integration point for GitHub Actions workflows.
Inputs¶
Parse Action Inputs¶
| Input | Description | Required | Example |
|---|---|---|---|
job | Job name | Yes | rucio, evnt, truth3, coffea, eventloop-columnar, eventloop-standard, fastframes |
log-file | Path to log file | Yes | rucio.log, log.generate, log.Derivation, log.EVNTtoDAOD, coffea_hist.log, eventloop_arrays.log, eventloop_noarrays.log, fastframes.log |
log-type | Type of log parser to use | Yes | rucio, evnt, truth3, coffea, eventloop, fastframes |
cluster | Cluster name | Yes | UC-AF, SLAC-AF, BNL-AF |
kibana-token | Token for benchmark ID | Yes | From secrets |
kibana-kind | Kind for benchmark ID | Yes | "benchmark" (literal value) |
host | Hostname to identify the machine | Yes | ${NODE_NAME} |
payload-file | Path to payload file for size calculation | No | Empty string (default, payloadSize = -1) |
os | Operating system the job ran on | Yes | alma9, centos7 |
mode | Job execution mode | Yes | batch, interactive |
containerized | Whether the job ran inside a container | Yes | "true", "false" |
output-file | Output JSON file path | No | payload.json (default) |
Upload Action Inputs¶
| Input | Description | Required | Example |
|---|---|---|---|
payload-file | Path to JSON payload file | Yes | payload.json |
kibana-uri | URI endpoint for LogStash | Yes | From secrets |
Implementation Steps¶
Parse Action¶
The parse action performs these steps:
- Setup pixi: Sets up the
kibanapixi environment with Python 3.13 and required dependencies - Parse log file: Runs the parsing script to generate JSON payload:
pixi run -e kibana python -m parsing.scripts.ci_parse \
--job <job> \
--log-file <log-file> \
--log-type <log-type> \
--cluster <cluster> \
--token <token> \
--kind <kind> \
--host <host> \
--os <os> \
--mode <mode> \
--containerized <containerized> \
--output payload.json
- Output: Generates
payload.jsonfile in the workspace
Upload Action¶
The upload action performs these steps:
- Validate payload file: Checks that the payload file exists and displays its contents for debugging
- Upload to LogStash: POSTs the JSON payload using curl:
curl -X POST "<kibana-uri>" \
-H "Content-Type: application/json" \
-d @payload.json \
-w "%{http_code}" \
-s -o response.txt
- Verify response: Checks HTTP status code and fails if not 2xx
Data Structure¶
The parsed data sent to LogStash (which routes it to Kibana) is validated against a JSON schema (parsing/schema/payload.schema.json) to ensure correctness before upload.
Required structure:
{
"submitTime": 1787510063000,
"queueTime": 0,
"runTime": 337,
"status": 0,
"setupTime": 4,
"cpuPercent": 95.0,
"maxRssKb": 1600792,
"job": "evnt",
"cluster": "UC-AF",
"payloadSize": -1,
"token": "***",
"kind": "benchmark",
"host": "g006.af.uchicago.edu",
"os": "alma9",
"mode": "batch",
"containerized": false
}
Field Descriptions¶
| Field | Type | Description | Source |
|---|---|---|---|
job | String | Job name (e.g., rucio, evnt, truth3) | Passed from workflow as a literal value |
cluster | String | AF cluster name (UC-AF, SLAC-AF, BNL-AF) | Passed from workflow |
submitTime | Integer | UTC timestamp (ms since epoch) | Parsed from log |
queueTime | Integer | Queue time (seconds) | Parsed from log |
runTime | Integer | Execution time (seconds) | Parsed from log |
payloadSize | Integer | Output file size (bytes) | Calculated from payload-file input using Path().stat().st_size (-1 if not provided, 0 for empty file) |
status | Integer | Exit code (0=success, non-zero=failure) | Parsed from log |
host | String | Hostname where job executed (idn-hostname format) | Passed from workflow via the NODE_NAME environment variable |
setupTime | Integer | Environment setup time (seconds), optional | Parsed from log, when present |
cpuPercent | Number | CPU usage percentage, optional | Parsed from /usr/bin/time -v output, when present |
maxRssKb | Integer | Maximum resident set size (KB), optional | Parsed from /usr/bin/time -v output, when present |
os | String | Operating system (centos7, alma9) | Passed from workflow as a literal value |
mode | String | Job execution mode (batch, interactive) | Passed from workflow as a literal value |
containerized | Boolean | Whether the job ran inside a container | Passed from workflow as a literal value |
token | String | Benchmark identifier AND LogStash routing key | Passed from workflow (secrets, with a hardcoded fallback) |
kind | String | Benchmark type AND LogStash routing kind | Passed from workflow as a literal value |
Static vs Parsed Fields¶
Static fields (from workflow configuration):
job- Job name, a literal value per jobcluster- Set per site (UC-AF, SLAC-AF, etc.)token- Benchmark identifier token AND LogStash routing keykind- Benchmark kind/category AND LogStash routing kindhost- Hostname from theNODE_NAMEenvironment variableos- Operating system, a literal value per jobmode- Execution mode, a literal value per jobcontainerized- Whether the job runs in a container, a literal value per job
Parsed fields (extracted from logs):
submitTime- Start timestamp from logqueueTime- Time waiting before executionrunTime- Total execution durationpayloadSize- Size of output filesstatus- Job exit codesetupTime- Environment setup time, when present in the logcpuPercent- CPU usage percentage, when present in the logmaxRssKb- Maximum resident set size, when present in the log
Note: The token and kind fields are included in the JSON document body sent to LogStash, where they serve the dual purpose of identifying the benchmark and routing the data to the appropriate Kibana instance.
Failure Handling¶
Neither action uses continue-on-error, so a parsing or upload failure fails the benchmark job:
- A parsing failure fails the job — the
parse benchmark logstep'sif: always()only guarantees it runs even if the benchmark script failed, not that its own failure is tolerated - An upload failure fails the job the same way
- Logs are still uploaded as artifacts regardless, since the "upload log" step also uses
if: always() - Parsing and upload errors are visible in the workflow's job logs
Usage Example¶
- name: parse benchmark log
if: always() # Run even if benchmark failed
uses: ./.github/actions/parse
with:
job: rucio
log-file: rucio.log
log-type: rucio
cluster: UC-AF
kibana-token: ${{ secrets.KIBANA_TOKEN || 'default-token' }}
kibana-kind: "benchmark"
host: ${{ env.NODE_NAME }}
os: alma9
mode: batch
containerized: "false"
- name: upload to kibana
if: always() # Run even if parsing failed
uses: ./.github/actions/upload
with:
payload-file: payload.json
kibana-uri: ${{ secrets.KIBANA_URI }}
LogStash/Elasticsearch Configuration¶
Data is sent to LogStash, which routes it to Elasticsearch:
- LogStash endpoint: this acts as routing service
- Elasticsearch index:
af_benchmarks - Protocol: HTTPS
- Routing/Authentication: Via
token+kindfields in the JSON document body
The token and kind fields serve a dual purpose:
- Benchmark identification: Uniquely identify and categorize the benchmark run
- LogStash routing: Direct the data to the appropriate Kibana instance
This body-based routing mechanism eliminates the need for traditional HTTP authentication headers or stored credentials.
Debugging¶
Viewing Parsing Logs¶
- Go to the workflow run in GitHub Actions
- Click on the specific job
- Expand the "parse benchmark log" or "upload to kibana" step
- Review the output for parsing errors
Common Parsing Issues¶
Log file not found:
- Verify the log file path matches the actual output
- Check that the benchmark script completed
- Look for the file in workflow artifacts
Parsing errors:
- Check log file format matches expected structure
- Verify parsing script handles this job type
- Review error messages in workflow logs
Upload failures:
- Check network connectivity to LogStash endpoint
- Verify
tokenandkindvalues are correctly set - Ensure the
af_benchmarksindex exists in Elasticsearch
Testing Locally¶
Test parsing and upload separately:
Test parsing:
pixi shell -e kibana
python -m parsing.scripts.ci_parse \
--job rucio \
--log-file path/to/rucio.log \
--log-type rucio \
--cluster UC-AF \
--token $KIBANA_TOKEN \
--kind $KIBANA_KIND \
--host $HOSTNAME \
--os alma9 \
--mode batch \
--containerized false \
--output payload.json
Test upload:
curl -X POST "$KIBANA_URI" \
-H "Content-Type: application/json" \
-d @payload.json \
-w "\nHTTP Status: %{http_code}\n"
Integration with Other Workflows¶
These actions are currently used by:
- UChicago Benchmark Workflow - All 11 benchmark jobs
Can be extended to:
- SLAC benchmark workflows
- BNL benchmark workflows
- NERSC benchmark workflows