Skip to content

[DevOps]: Add Chrysalis ingestion wrapper and deploy - #169

Merged
tomvothecoder merged 25 commits into
E3SM-Project:mainfrom
tomvothecoder:feature/154-ingestion-sites
Sep 10, 2026
Merged

tomvothecoder merged 25 commits into
E3SM-Project:mainfrom
tomvothecoder:feature/154-ingestion-sites

Conversation

@tomvothecoder

@tomvothecoder tomvothecoder commented Apr 30, 2026

Copy link
Copy Markdown
Collaborator

Description

This PR replaces the one-off NERSC host wrapper with a config-driven site collection launcher and adds Chrysalis support for remote, upload-based metadata ingestion. The launcher keeps scheduler/site setup in shell configuration while selecting the appropriate shared Python runner: path-based ingestion at NERSC and upload-based ingestion at Chrysalis.

  • Closes [DevOps]: Implement remote data collection job at Chrysalis for ingestion #154
  • Closes [DevOps]: Explore simple cron usage for data collection on Chrysalis #269
  • Adds sites/site_ingestion_launcher.sh, which loads a named site config, applies staging/archive scan mode, serializes runs with a lock, loads protected API configuration when required, and invokes the configured Python module.
  • Adds sites/chrysalis.config for the Chrysalis archive roots, chrysalis machine identity, upload ingestor, API/token-file locations, and archive scan lower bound.
  • Adds sites/nersc.config and removes the superseded sites/nersc.sh and NERSC-specific crontab example.
  • Uses hpc_upload_archive_ingestor.py for non-NERSC sites, packaging each eligible case and submitting it to /api/v1/ingestions/from-hpc-upload; NERSC continues to use nersc_archive_ingestor.py.
  • Adds state-aware dry runs: dry runs read remote state and archive checkpoints by default; DRY_RUN_USE_REMOTE_STATE=false supports credential-free offline discovery.
  • Restores safe dry-run config defaults, validates both root-derived and explicit launcher paths, corrects the cron example, and replaces temporary debug logging with run-scoped logs.
  • Documents scan modes, operational validation, launcher configuration, Chrysalis onboarding, and an example crontab.
  • Adds launcher and ingestor test coverage, including offline launcher behavior, root configuration, missing-path errors, and dry-run remote-state behavior.

Task

Finish turning this branch into a deployable ingestion path for Chrysalis first, then use the same pattern for other sites once access is available.

  • Review the current branch implementation and keep ingestion logic in Python, not shell wrappers.
  • Validate the Chrysalis archive path and Jenkins runtime assumptions.
  • Confirm how SIMBOARD_API_BASE_URL and SIMBOARD_API_TOKEN should be stored and injected in the Chrysalis Jenkins job.
  • Run the Chrysalis wrapper in dry-run mode and verify archive access, network egress to SimBoard, and candidate counts.
  • Enable non-dry-run ingestion only after the dry-run output is validated.
  • Perform a full-run in progress
  • Follow-up Work (Open new GitHub issue)
    • Apply for or confirm accounts/access for Frontier, Aurora, and Compy.
    • After Chrysalis works, add equivalent thin wrappers for the remaining sites as access allows.

Checklist

  • Code follows project style guidelines
  • Self-reviewed code
  • No new warnings
  • Tests added or updated (if needed)
  • All tests pass (locally and CI/CD)
  • Documentation/comments updated (if needed)
  • Breaking change noted (if applicable)

Deployment Notes (if any)

No special deployment steps.

@tomvothecoder
tomvothecoder requested a review from TonyB9000 May 6, 2026 22:33
@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder Once I am clear on the boundaries to the term "NERSC ingestion wrapper", I should be able to comprehent "Chrysalis ingestion wrapper". The term "scheduler-agnostic" refers to Jenkins? (I always considered cron to be universal...).

@TonyB9000 TonyB9000 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Configures a call to the hpc_archive_ingestor. Understandable.

What process sets "SIMBOARD_API_BASE_URL" and "SIMBOARD_API_TOKEN"?

@TonyB9000

Copy link
Copy Markdown
Collaborator

Hmmmm. The "Tom Requested your review" took me to the page with 7 files to examine, each with a "submit-review" option. As soon as I completed the first one, all 7 vanished...

@tomvothecoder

tomvothecoder commented May 13, 2026

Copy link
Copy Markdown
Collaborator Author

Configures a call to the hpc_archive_ingestor. Understandable.

What process sets "SIMBOARD_API_BASE_URL" and "SIMBOARD_API_TOKEN"?

Hmmmm. The "Tom Requested your review" took me to the page with 7 files to examine, each with a "submit-review" option. As soon as I completed the first one, all 7 vanished...

Accidentally tagged you for review. I meant to assign this PR you. It is fixed now.

@tomvothecoder tomvothecoder added the type: enhancement New feature or request label May 13, 2026
@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder "Accidentally tagged you for review". OK, (I think colleges should offer a master's program in github).

@tomvothecoder

Copy link
Copy Markdown
Collaborator Author

Chrysalis and other non-NERSC sites require upload-based ingestion rather than path-based ingestion, so follow-up work is tracked in #207 for a state-first HPC upload flow with DB-backed dedupe parity.

@TonyB9000

Copy link
Copy Markdown
Collaborator

Using the "upload-based' vs "path-based" terminology, my thought was that when the NERSC upload-receiving system was deliverd an upload from a non-NERSC system, it could open it in the existing NERSC PA-directory under (say) "From_crysalis/<new_exec_ids>" and then process it with the existing "path-based" codes - assuming PACE would not interfere with it (and vice-versa). But on second thought, to avoid PACE crossing, it would be best to open it in a separate "PACE-unaware" directory.

@TonyB9000

TonyB9000 commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

@tomvothecoder I am preparing to exercise "hpc_upload_archive_ingestor.py" on chrysalis, to see the logs and flow (in dry-run) in action, discover parameter faults, etc.

QUESTION: Although, on NERSC, the backend ingestion is "path-based" (returnsp paths for ingestion), it could in principle run the "https-transfer-based" codes just as easily. I might try a dryrun on NESRC/Perlmutter first, since that configuration is already a known item. Then, differences in behavior on chrysalis would stand out. Does that make sense?

@tomvothecoder
tomvothecoder force-pushed the feature/154-ingestion-sites branch from 22a1a88 to feae197 Compare June 4, 2026 20:26
@TonyB9000

TonyB9000 commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

@tomvothecoder Apologies if I'm doing this wrong.

I attempted to test the "hpc_upload" on NESRC, thinking "--help" might be helpful. To get started, I needed an environment where I could install things, so:

After

    python3.11 -m venv ~/envs/test_simboard
    source ~/envs/test_simboard/bin/activate
    python3.11 -m pip install --upgrade pip

    python3.11 -m pip install python-dateutil
    pip install pydantic
    pip install fastapi_users
 
The (bash script) commands:

    REPO_ROOT="/global/homes/t/tonyb/gitrepo/simboard/backend"
    SCRIPT="$REPO_ROOT/app/scripts/ingestion/hpc_upload_archive_ingestor.py"

    PYTHONPATH="$REPO_ROOT"
    python3.11 "$SCRIPT" --help

Produces the following output:

2026-06-04 15:02:26,464 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-04T22:02:26.464377+00:00 event=run_started archive_root=/performance_archive mode=ingest
2026-06-04 15:02:26,464 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-04T22:02:26.464638+00:00 event=startup_configuration_begin
2026-06-04 15:02:26,464 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-04T22:02:26.464749+00:00 event=summary_table row_count=10 rows="api.api_base_url=http://backend:8000 | api.endpoint_url=http://backend:8000/api/v1/ingestions/from-hpc-upload | api.state_endpoint_url=http://backend:8000/api/v1/ingestions/state | paths.archive_root=/performance_archive | runtime.machine_name=perlmutter | runtime.dry_run=false | runtime.max_cases_per_run=null | runtime.max_attempts=3 | runtime.request_timeout_seconds=60 | auth.has_api_token=false" title=startup_configuration
2026-06-04 15:02:26,464 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-04T22:02:26.464819+00:00 event=startup_configuration_end
2026-06-04 15:02:26,464 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-04T22:02:26.464876+00:00 event=archive_root_missing archive_root=/performance_archive
2026-06-04 15:02:26,464 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-04T22:02:26.464929+00:00 event=run_finished duration_seconds=0.001 exit_code=1 mode=ingest

I now see there is no commandline parsing.  I need to set “dry_run” as an environment variable so that the auto-generated config will pick it up.  I must have missed where the docs explain setting the environment variables.  I assume I can set them in my “run_script”.

Comment thread backend/app/scripts/ingestion/sites/chrysalis.sh Outdated
@tomvothecoder

tomvothecoder commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator Author

Hey Tony, happy to help and no apologies needed.

I attempted to test the "hpc_upload" on NESRC, thinking "--help" might be helpful. To get started, I needed an environment where I could install things, so:

After

    python3.11 -m venv ~/envs/test_simboard
    source ~/envs/test_simboard/bin/activate
    python3.11 -m pip install --upgrade pip

    python3.11 -m pip install python-dateutil
    pip install pydantic
    pip install fastapi_users
 

SimBoard defines the Python backend dependencies in pyproject.toml and uses uv for dependency management.

You can run make backend-install if you only need a Python env (source).

The (bash script) commands:

REPO_ROOT="/global/homes/t/tonyb/gitrepo/simboard/backend"
SCRIPT="$REPO_ROOT/app/scripts/ingestion/hpc_upload_archive_ingestor.py"

PYTHONPATH="$REPO_ROOT"
python3.11 "$SCRIPT" --help

Produces the following output:

2026-06-04 15:02:26,464 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-04T22:02:26.464377+00:00 event=run_started archive_root=/performance_archive mode=ingest
2026-06-04 15:02:26,464 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-04T22:02:26.464638+00:00 event=startup_configuration_begin
2026-06-04 15:02:26,464 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-04T22:02:26.464749+00:00 event=summary_table row_count=10 rows="api.api_base_url=http://backend:8000 | api.endpoint_url=http://backend:8000/api/v1/ingestions/from-hpc-upload | api.state_endpoint_url=http://backend:8000/api/v1/ingestions/state | paths.archive_root=/performance_archive | runtime.machine_name=perlmutter | runtime.dry_run=false | runtime.max_cases_per_run=null | runtime.max_attempts=3 | runtime.request_timeout_seconds=60 | auth.has_api_token=false" title=startup_configuration
2026-06-04 15:02:26,464 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-04T22:02:26.464819+00:00 event=startup_configuration_end
2026-06-04 15:02:26,464 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-04T22:02:26.464876+00:00 event=archive_root_missing archive_root=/performance_archive
2026-06-04 15:02:26,464 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-04T22:02:26.464929+00:00 event=run_finished duration_seconds=0.001 exit_code=1 mode=ingest

I now see there is no commandline parsing. I need to set “dry_run” as an environment variable so that the auto-generated config will pick it up. I must have missed where the docs explain setting the environment variables. I assume I can set them in my “run_script”.

I'd checkout this branch now that I've rebased it on the latest main commit.

The chrysalis.sh bash script exports environment variables and wraps hpc_upload_archive_ingestor.py. You can try experimenting with that script. More info here: https://github.com/tomvothecoder/simboard/tree/feature/154-ingestion-sites/backend/app/scripts#hpc-upload-archive-ingestor.

@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder I get the latest stuff - but I have made progress. My latest run_script (NERSC dry_run test) says:

REPO_ROOT="/global/homes/t/tonyb/gitrepo/simboard/backend"
WORKDIR="/global/homes/t/tonyb/test/simboard"
SCRIPT="$REPO_ROOT/app/scripts/ingestion/hpc_upload_archive_ingestor.py"

export PYTHONPATH="$REPO_ROOT"
export DRY_RUN=True
python3.11 "$SCRIPT"

The output indicates that I am missing "archive_root” and “has_api_token”.

By examining the "nersc" "_build_config" function, I can see what variables exist to push into the environment.

I'll checkout branch #169 on both NERSC and Chrysalis to do comparisons in outputs.

@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder git gets me again:

You wrote: "I'd checkout this branch now that I've rebased it on the latest main commit."

is "this branch" off of main, as you had advised? Or is it off of a fork??

((test_simboard) ) (base) [ac.bartoletti1@chrlogin1 simboard]$ git branch -a

  • main
    remotes/origin/HEAD -> origin/main
    remotes/origin/copilot/analyze-simboard-devops-issues
    remotes/origin/copilot/check-copilot-agent-tokens
    remotes/origin/copilot/enhance-simulation-details-page
    remotes/origin/copilot/fix-hpc-filepaths-issue
    remotes/origin/dev-ai
    remotes/origin/fix/181-archive-path-substitution
    remotes/origin/main

When I get too confused, I do a clean "git clone". Then I can do one of these:

To pull a remote branch down from remote:

    git fetch --all --prune
    git checkout -b newbranchname origin/newbranchname

To checkout a remote branch pushed but not merged to main/master

    git fetch origin <the_remote_branch_name>
    git checkout -b <any_new_local_name> origin/<the_remote_branch_name>

to fetch a branch from a remote fork: (example)

    git remote add tomvothecoder https://github.com/tomvothecoder/simboard.git
    git fetch tomvothecoder
    git checkout -b feature/154-ingestion-sites tomvothecoder/feature/154-ingestion-sites

Which is appropriate in this case?

@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder If I pull down a branch off of someone's fork, am I in that fork, or can I pull that into a new branch of my local main? The persistence of branches and forks, between local and remote, is a bit of a mystery.

@tomvothecoder

Copy link
Copy Markdown
Collaborator Author

@tomvothecoder If I pull down a branch off of someone's fork, am I in that fork, or can I pull that into a new branch of my local main? The persistence of branches and forks, between local and remote, is a bit of a mystery.

  • Upstream -> E3SM-Project/simboard
  • Fork -> tomvothecoder/simboard

This branch (tomvothecoder:feature/154-ingestion-sites) is on my fork (tomvothecoder/simboard), not on upstream (E3SM-Project/simboard) You need to add my fork as a remote git source to git checkout branches from my fork.

Something like this (I did not verify correctness):

git remote add tomvothecoder https://github.com/tomvothecoder/simboard
git checkout tomvothecoder feature/154-ingestion-ites 

I usually work directly on upstream and not fork when possible, but in this case I use a fork for separate testing purposes.

@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder Sorry, I guess I must pull from your fork.

Quick test: I cd to ``/home/ac.bartoletti1/gitrepo/simboard/backend" and issue

python3.12 -m app.scripts.ingestion.nersc_archive_ingestor --api-base-url http://backend:8000 --machine-name chrysalis

The result:

2026-06-05 16:23:29,964 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-05T21:23:29.964677+00:00 event=run_started archive_root=/performance_archive mode=ingest
2026-06-05 16:23:29,964 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-05T21:23:29.964921+00:00 event=startup_configuration_begin
2026-06-05 16:23:29,965 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-05T21:23:29.965032+00:00 event=summary_table row_count=10 rows="api.api_base_url=_fake_url_ | api.endpoint_url=_fake_url_/api/v1/ingestions/from-path | api.state_endpoint_url=_fake_url_/api/v1/ingestions/state | paths.archive_root=/performance_archive | runtime.machine_name=perlmutter | runtime.dry_run=false | runtime.max_cases_per_run=null | runtime.max_attempts=3 | runtime.request_timeout_seconds=60 | auth.has_api_token=true" title=startup_configuration
2026-06-05 16:23:29,965 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-05T21:23:29.965082+00:00 event=startup_configuration_end
2026-06-05 16:23:29,965 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-05T21:23:29.965118+00:00 event=archive_root_missing archive_root=/performance_archive
2026-06-05 16:23:29,965 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-05T21:23:29.965168+00:00 event=run_finished duration_seconds=0.0 exit_code=1 mode=ingest

@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder The line in "chrysalis.sh"

script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"; echo $script_dir
(prints "/home/ac.bartoletti1/test/simboard")

clearly wont work for defining "backend_root" as backend_root="$(cd "${script_dir}/../../../.." && pwd)"

I will modify chrysalis.sh to provide a "backend_root" that does not depend upon the user location., at least for test purposes.

@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder Works better now that it can find "backentd/apps"

When I use this for "chrysalis.sh":

GITREPO="/home/ac.bartoletti1/gitrepo"

: "${SIMBOARD_API_BASE_URL:?SIMBOARD_API_BASE_URL is required}"
: "${SIMBOARD_API_TOKEN:?SIMBOARD_API_TOKEN is required}"

export MACHINE_NAME="${MACHINE_NAME:-chrysalis}"
export PERF_ARCHIVE_ROOT="${PERF_ARCHIVE_ROOT:-/lcrc/group/e3sm/PERF_Chrysalis/performance_archive}"
export STATE_PATH="${STATE_PATH:-${PERF_ARCHIVE_ROOT}/../simboard-ingestion-state.json}"
export DRY_RUN="${DRY_RUN:-true}"

backend_root="$GITREPO/simboard/backend"
python_bin="${PYTHON_BIN:-python}"

cd "${backend_root}"
exec "${python_bin}" -m app.scripts.ingestion.hpc_upload_archive_ingestor

and issue these exports:

export SIMBOARD_API_BASE_URL=" http://backend:8000"
export SIMBOARD_API_TOKEN="_fake_token_"

I get:

2026-06-05 17:17:50,763 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-05T22:17:50.763649+00:00 event=run_started archive_root=/lcrc/group/e3sm/PERF_Chrysalis/performance_archive mode=dry-run
2026-06-05 17:17:50,763 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-05T22:17:50.763967+00:00 event=startup_configuration_begin
2026-06-05 17:17:50,764 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-05T22:17:50.764094+00:00 event=summary_table row_count=10 rows="api.api_base_url=\" http://backend:8000\" | api.endpoint_url=\" http://backend:8000/api/v1/ingestions/from-hpc-upload\" | api.stat
e_endpoint_url=\" http://backend:8000/api/v1/ingestions/state\" | paths.archive_root=/lcrc/group/e3sm/PERF_Chrysalis/performance_archive | runtime.machine_name=chrysalis | runtime.dry_run=true | runtime.max_cases_per_run=null | runtime.max_attempts=3 | runtime.request_timeout_second
s=60 | auth.has_api_token=true" title=startup_configuration
2026-06-05 17:17:50,764 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-05T22:17:50.764155+00:00 event=startup_configuration_end
2026-06-05 17:17:50,816 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-05T22:17:50.816097+00:00 event=state_fetch_failed error="URL error: [Errno -2] Name or service not known" machine_name=chrysalis status_code=null
2026-06-05 17:17:50,816 [INFO]: nersc_archive_ingestor.py(_log_event:1344) >> ts=2026-06-05T22:17:50.816211+00:00 event=run_finished duration_seconds=0.053 exit_code=1 mode=dry-run

I guess, even "dry_run" requires real URLs and API_tokens. That is because we need "state" up front.

@TonyB9000

TonyB9000 commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

Hi @tomvothecoder The document also says:

One-case-per-request rule:

  • Each upload request contains exactly one case directory.
  • case_path is sent alongside the archive and becomes the stable dedupe key in the ingestion audit table.
  • Browser/manual uploads still use /api/v1/ingestions/from-upload; this runner does not call that endpoint.

The term "alongside the archive" is a bit ambiguous. Would this be accurate?

  • Each upload request contains exactly one case directory, and one or more newly-completed jlid archives.
  • case_path is sent alongside the archives, and (case_id + jlid) becomes the stable dedupe key in the ingestion audit table.
  • Browser/manual uploads still use /api/v1/ingestions/from-upload; this runner does not call that endpoint.

Or am I misunderstanding the intent?

@tomvothecoder

tomvothecoder commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator Author

I guess, even "dry_run" requires real URLs and API_tokens. That is because we need "state" up front.

Great to see the progress!

Yes, the dry run needs to query the SimBoard database via the REST API. I will send the API_TOKEN over encrypted email to you.

Hi @tomvothecoder The document also says:

One-case-per-request rule:

* Each upload request contains exactly one case directory.

* case_path is sent alongside the archive and becomes the stable dedupe key in the ingestion audit table.

* Browser/manual uploads still use /api/v1/ingestions/from-upload; this runner does not call that endpoint.

The term "alongside the archive" is a bit ambiguous. Would this be accurate?

* Each upload request contains exactly one case directory, and one or more newly-completed jlid archives.

* case_path is sent alongside the archives, and (case_id + jlid) becomes the stable dedupe key in the ingestion audit table.

* Browser/manual uploads still use /api/v1/ingestions/from-upload; this runner does not call that endpoint.

Or am I misunderstanding the intent?

Your info sounds more accurate, thanks for the suggestion. Can you point me to the source document with this info? I will update it.

@TonyB9000

TonyB9000 commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

@tomvothecoder Running "chrysalis.sh" with the full (DRY_RUN) parameters yieded the following summary (folded for readability):

event=summary_table
    row_count=9
    rows="mode=dry-run 
        | discovered_cases=746 
        | candidate_cases=746 
        | execution_dirs_scanned=3155 
        | execution_dirs_accepted=1649 
        | skipped_incomplete=1506
        | skipped_invalid=0 
        | candidate_logs_emitted=20 
        | candidate_logs_suppressed=726" 
    title=dry_run_summary
event=run_finished
    duration_seconds=374.342
    exit_code=0
    mode=dry-run

Questions that arise:

  • What distinguishes "discovered cases" from "candidate cases"?
  • What distinguishes "skipped_incomplete" from "skipped_invalid"?
  • Where is "skipped_already_accepted = 0"? Perhaps this test is unrealistic, as no "state" of previous accepted submissions exists,.
  • Why is there no count of "state" returned from the database? Was the query restricted to Chrysalis-only? Why is the DB query not indicated?
  • What is "candidate_logs_emitted/suppressed"?

Observation: The bulk of work getting to this point involved stuffing the right ENV VARS and having created an environment where misc modules like "dateutils" could be installed. On Chrysalis, I performed

    python3.12 -m venv ~/envs/test_simboard
    source ~/envs/test_simboard/bin/activate
    python3.12 -m pip install --upgrade pip

    python3.12 -m pip install python-dateutil
    pip install pydantic
    pip install fastapi_users

On NERSC/Perlmutter, I simply replaced "python3.12" with "python3.11". I intend to perform the same test on Perlmutter, just to exercise the mechanisms of networking.

@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder For comparison, running the equivalent commands on perlmutter (swapping our parameters where necessary), we obtain the summary:

event=summary_table
    row_count=9 
    rows="mode=dry-run 
        | discovered_cases=1289 
        | candidate_cases=1289 
        | execution_dirs_scanned=2514 
        | execution_dirs_accepted=1648 
        | skipped_incomplete=866 
        | skipped_invalid=0
        | candidate_logs_emitted=20
        | candidate_logs_suppressed=1269"
    title=dry_run_summary
event=run_finished
    duration_seconds=23.689
    exit_code=0
    mode=dry-run

I suppose I should re-run the chrysalis test, using "OLD_PERF" as the root_PA directory. It is HUGE.

@tomvothecoder

tomvothecoder commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator Author

What distinguishes "discovered cases" from "candidate cases"?

discovered_casesare everything the archive scan finds that looks like a case.
candidate_cases are the subset that SimBoard does not already know about and may ingest.

Since this is a first-time dry-run on the Chrysalis performance_archive directory, it is expected that discovered_cases and candidates_cases are the same.

What distinguishes "skipped_incomplete" from "skipped_invalid"?

skipped_incomplete means required metadata was missing.
skipped_invalid means the metadata or path looked wrong, unreadable, or unusable.

Where is "skipped_already_accepted = 0"? Perhaps this test is unrealistic, as no "state" of previous accepted submissions exists.

That exact counter is not in nersc_archive_ingestor.py. The script checks existing SimBoard ingestion state and filters out already-known execution IDs, but it does not use the term “accepted” or expose a skipped_already_accepted count.

So yes: a test expecting that exact field is probably unrealistic or stale.

Why is there no count of "state" returned from the database? Was the query restricted to Chrysalis-only? Why is the DB query not indicated?

The ingestor script only asks SimBoard for enough existing ingestion state to decide which archive cases and their executions are new and may be candidates for ingestion. It does not fetch, return, or summarize the full database state. It also does not show the database query because the query is behind the SimBoard API, not inside the ingestor script. So this is not a Chrysalis-specific DB query in the ingestor. It is an API request filtered by the configured machine_name.

If more detail is needed, the API response or ingestor summary would need to be expanded to include counts like total known cases, known execution IDs, skipped known cases, and machine filter used.

Happy for you to open a new GitHub issue to expand logging in https://github.com/E3SM-Project/simboard/blob/main/backend/app/scripts/ingestion/nersc_archive_ingestor.py and https://github.com/E3SM-Project/simboard/blob/main/backend/app/scripts/ingestion/hpc_upload_archive_ingestor.py.

What is "candidate_logs_emitted/suppressed"?

What is candidate_logs_emitted/suppressed?

They are dry-run logging counters.

candidate_logs_emitted = how many candidate case details were actually printed to the log.

candidate_logs_suppressed = how many candidate case details were not printed because the script hit its logging limit.

The point is to avoid massive logs when many candidate cases are found. It does not change which cases are candidates or which cases would be ingested.

@tomvothecoder

Copy link
Copy Markdown
Collaborator Author

I suppose I should re-run the chrysalis test, using "OLD_PERF" as the root_PA directory. It is HUGE.

I don't think this is going to work yet as the directory structure of "OLD_PERF" is different from "performance_archive".
We need to expand ingestion support for "OLD_PERF" in #209.

We might also want to be targeted in what we ingest from "OLD_PERF". This will require guidance Rob/Jill.

@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder Job Completed after ~20 minutes. 15 new successes, which I assume include the 11 past failures (not trivial to tell without a more structured summary).

The last lines of the log are as follows:

2026-08-31 11:11:14,165 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=run_completed accepted_execution_ids=320 deferred_execution_ids=0 execution_dirs_accepted=320 execution_dirs_scanned=28 failure_count=0 rejected_existing_execution_ids=1604 rejected_incomplete_execution_ids=2150 rejected_invalid_execution_ids=0 scanned_cases=15 selected_submission_cases=15 skipped_incomplete=2150 skipped_invalid=0 skipped_transient=0 submission_qualified_cases=15 success_count=15 transient_execution_ids=0
2026-08-31 11:11:14,166 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=run_summary_counts mode=ingest scanned_cases=15 submission_qualified_cases=15 selected_submission_cases=15 execution_dirs_scanned=28 execution_dirs_accepted=320 skipped_incomplete=2150 skipped_invalid=0 skipped_transient=0
2026-08-31 11:11:14,166 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=run_summary_outcomes success_count=15 failure_count=0 accepted_execution_ids=320 rejected_existing_execution_ids=1604 rejected_incomplete_execution_ids=2150 rejected_invalid_execution_ids=0 transient_execution_ids=0 deferred_execution_ids=0
2026-08-31 11:11:14,167 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=run_finished mode=ingest scan_mode=staging exit_code=0 duration_seconds=1260.724

@tomvothecoder

Copy link
Copy Markdown
Collaborator Author

@tomvothecoder I have re-enabled the crontab for its 15-minute "staging" runs, and it should begin in just a minute.

Where are there upload-limits being enforced? Do I need to pull-down code changes to collection before the new runs? I guess I'll soon find out.

I merged #308 and updated the upload limit on the NERSC backend to fix this. Nothing needed on your end.

@tomvothecoder Job Completed after ~20 minutes. 15 new successes, which I assume include the 11 past failures (not trivial to tell without a more structured summary).

The last lines of the log are as follows:

2026-08-31 11:11:14,165 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=run_completed accepted_execution_ids=320 deferred_execution_ids=0 execution_dirs_accepted=320 execution_dirs_scanned=28 failure_count=0 rejected_existing_execution_ids=1604 rejected_incomplete_execution_ids=2150 rejected_invalid_execution_ids=0 scanned_cases=15 selected_submission_cases=15 skipped_incomplete=2150 skipped_invalid=0 skipped_transient=0 submission_qualified_cases=15 success_count=15 transient_execution_ids=0
2026-08-31 11:11:14,166 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=run_summary_counts mode=ingest scanned_cases=15 submission_qualified_cases=15 selected_submission_cases=15 execution_dirs_scanned=28 execution_dirs_accepted=320 skipped_incomplete=2150 skipped_invalid=0 skipped_transient=0
2026-08-31 11:11:14,166 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=run_summary_outcomes success_count=15 failure_count=0 accepted_execution_ids=320 rejected_existing_execution_ids=1604 rejected_incomplete_execution_ids=2150 rejected_invalid_execution_ids=0 transient_execution_ids=0 deferred_execution_ids=0
2026-08-31 11:11:14,167 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=run_finished mode=ingest scan_mode=staging exit_code=0 duration_seconds=1260.724

You can try cross-checking the failing case ids against the latest logs here to see if they are not listed as successful. You have the failing case ids listed here: #169 (comment).

I will also send you read-only SQL credentials and instructions over email to try SQL queries for this information. I recommend using an agentic AI code tool like GitHub Copilot (E3SM provides access with a seat on the org account) because they have the context of the repository to develop SQL queries for you to run. My SQL skills are rusty so that is what I'm doing. Of course, it's another software you'd need to learn.

@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder It appears that all 11 are successful - at least no errors were generated.

I isolated each case_id and fed them to a script that does:

    echo -n "CASE=$acase: "
    grep $acase $logfile | cut -c77- | tail -1

which is an admittedly crude way to review the logs.

The results are:

CASE=v2.LR.BGC-LNDATM.FireUpdate: event=case_ingested case_path=/lcrc/group/e3sm/PERF_Chrysalis/performance_archive/performance_archive_chrysalis_e3sm_2025_03_30_00_47_18/ac.sfeng1/v2.LR.BGC-LNDATM.FireUpdate attempts=1 created_count=41 duplicate_count=0 error_count=0
CASE=20250401.F2010.ne4_oQU240.dtcl_O1_n0030: event=case_ingested case_path=/lcrc/group/e3sm/PERF_Chrysalis/performance_archive/performance_archive_chrysalis_e3sm_2025_04_08_00_47_41/ac.mkelleher/20250401.F2010.ne4_oQU240.dtcl_O1_n0030_31_60 attempts=1 created_count=1 duplicate_count=0 error_count=0
CASE=20250401.F2010.ne4_oQU240.dtcl_O1_n0030_31_60: event=case_ingested case_path=/lcrc/group/e3sm/PERF_Chrysalis/performance_archive/performance_archive_chrysalis_e3sm_2025_04_08_00_47_41/ac.mkelleher/20250401.F2010.ne4_oQU240.dtcl_O1_n0030_31_60 attempts=1 created_count=1 duplicate_count=0 error_count=0
CASE=20251218.BGWCYCL20TR.ne30pg2_r05_IcoswISC30E3r5_gis1to10r02.chrysalis.runoffSpreading150e300: event=case_ingested case_path=/lcrc/group/e3sm/PERF_Chrysalis/performance_archive/performance_archive_chrysalis_e3sm_2026_03_27_01_14_11/ac.sprice/20251218.BGWCYCL20TR.ne30pg2_r05_IcoswISC30E3r5_gis1to10r02.chrysalis.runoffSpreading150e300 attempts=1 created_count=62 duplicate_count=0 error_count=0
CASE=20251218.BGWCYCL20TR.ne30pg2_r05_IcoswISC30E3r5_gis1to10r02.chrysalis.runoffSpreading50e100: event=case_ingested case_path=/lcrc/group/e3sm/PERF_Chrysalis/performance_archive/performance_archive_chrysalis_e3sm_2026_03_27_01_14_11/ac.sprice/20251218.BGWCYCL20TR.ne30pg2_r05_IcoswISC30E3r5_gis1to10r02.chrysalis.runoffSpreading50e100 attempts=1 created_count=61 duplicate_count=0 error_count=0
CASE=20251218.BGWCYCL20TR.ne30pg2_r05_IcoswISC30E3r5_gis1to10r02.chrysalis.runoffSpreadingBaseline: event=case_ingested case_path=/lcrc/group/e3sm/PERF_Chrysalis/performance_archive/performance_archive_chrysalis_e3sm_2026_03_27_01_14_11/ac.sprice/20251218.BGWCYCL20TR.ne30pg2_r05_IcoswISC30E3r5_gis1to10r02.chrysalis.runoffSpreadingBaseline attempts=1 created_count=62 duplicate_count=0 error_count=0
CASE=v3.LR.ssp370_0261: event=case_collection_summary case=performance_archive_chrysalis_e3sm_2026_03_27_01_14_11/ac.smahajan/v3.LR.ssp370_0261 accepted=0 rejected_existing=2 rejected_incomplete=6 rejected_invalid=0 transient=0 deferred=0
CASE=20251203.BGWCYCL1850.ne30pg2_r05_IcoswISC30E3r5_gis1to10r02.chrysalis: event=case_collection_summary case=performance_archive_chrysalis_e3sm_2026_03_27_01_14_11/ac.sprice/20251203.BGWCYCL1850.ne30pg2_r05_IcoswISC30E3r5_gis1to10r02.chrysalis accepted=0 rejected_existing=4 rejected_incomplete=0 rejected_invalid=0 transient=0 deferred=0
CASE=20251208.IGELM_MLI.ne30pg2_r05_IcoswISC30E3r5_gis1to10r02.chrysalis: event=case_collection_summary case=performance_archive_chrysalis_e3sm_2026_03_27_01_14_11/ac.sprice/20251208.IGELM_MLI.ne30pg2_r05_IcoswISC30E3r5_gis1to10r02.chrysalis accepted=0 rejected_existing=2 rejected_incomplete=0 rejected_invalid=0 transient=0 deferred=0
CASE=20251107.v3.HR.piControl-Spinup: event=case_ingested case_path=/lcrc/group/e3sm/PERF_Chrysalis/performance_archive/performance_archive_chrysalis_e3sm_2026_03_27_01_14_11/ac.jwolfe/20251107.v3.HR.piControl-Spinup attempts=3 created_count=84 duplicate_count=0 error_count=0
CASE=20251209.IGELM_MLI.ne30pg2_r05_IcoswISC30E3r5_gis4to40.chrysalis: event=case_collection_summary case=performance_archive_chrysalis_e3sm_2026_03_27_01_14_11/ac.sprice/20251209.IGELM_MLI.ne30pg2_r05_IcoswISC30E3r5_gis4to40.chrysalis accepted=0 rejected_existing=1 rejected_incomplete=0 rejected_invalid=0 transient=0 deferred=0

@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder Progress Report: After running for about 30 minutes, the directory collection/assessment accumulated just over 2000 new cases, and consequently accepted over 5700 executions (2025-01 through 2026-09). It has spent most of the past 2 hours packaging and uploading the new archives, having completed 1138 of the 2000. It should complete by 4 PM (PST, or PDT, or whatever time it is really...)

@TonyB9000

Copy link
Copy Markdown
Collaborator

@tomvothecoder The chrysalis archive run completed, "duration_seconds=21458.032" (6 hrs).

The complete logfile (39989 lines) is here:

    /lcrc/group/e3sm2/simboard/operations/SBCS-archive-chrysalis-20260901_184501.log

The last 4 (run-summary) lines are here:

2026-09-01 19:42:53,851 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=run_completed accepted_execution_ids=5718 deferred_execution_ids=0 execution_dirs_accepted=5718 execution_dirs_scanned=10670 failure_count=0 rejected_existing_execution_ids=367 rejected_incomplete_execution_ids=5132 rejected_invalid_execution_ids=0 scanned_cases=3501 selected_submission_cases=3501 skipped_incomplete=5132 skipped_invalid=0 skipped_transient=0 submission_qualified_cases=3501 success_count=3501 transient_execution_ids=0
2026-09-01 19:42:53,852 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=run_summary_counts mode=ingest scanned_cases=3501 submission_qualified_cases=3501 selected_submission_cases=3501 execution_dirs_scanned=10670 execution_dirs_accepted=5718 skipped_incomplete=5132 skipped_invalid=0 skipped_transient=0
2026-09-01 19:42:53,852 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=run_summary_outcomes success_count=3501 failure_count=0 accepted_execution_ids=5718 rejected_existing_execution_ids=367 rejected_incomplete_execution_ids=5132 rejected_invalid_execution_ids=0 transient_execution_ids=0 deferred_execution_ids=0
2026-09-01 19:42:53,913 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=run_finished mode=ingest scan_mode=archive exit_code=0 duration_seconds=21458.032

(easier to read):

[event=run_completed]
accepted_execution_ids=5718
execution_dirs_accepted=5718
execution_dirs_scanned=10670
failure_count=0 
rejected_existing_execution_ids=367 
rejected_incomplete_execution_ids=5132
rejected_invalid_execution_ids=0
scanned_cases=3501
selected_submission_cases=3501
skipped_incomplete=5132
skipped_invalid=0
skipped_transient=0
submission_qualified_cases=3501
success_count=3501
transient_execution_ids=0

[event=run_summary_counts]
scanned_cases=3501 
submission_qualified_cases=3501 
selected_submission_cases=3501 
execution_dirs_scanned=10670 
execution_dirs_accepted=5718 
skipped_incomplete=5132 
skipped_invalid=0 
skipped_transient=0

[event=run_summary_outcomes]
success_count=3501 
failure_count=0 
accepted_execution_ids=5718 
rejected_existing_execution_ids=367 
rejected_incomplete_execution_ids=5132 
rejected_invalid_execution_ids=0 
transient_execution_ids=0 
deferred_execution_ids=0

[event=run_finished]
mode=ingest
scan_mode=archive
exit_code=0
duration_seconds=21458.032

@TonyB9000

TonyB9000 commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

ASIDE: A breakdown of the log file:

EVENT

archive_scan_progress                 37 lines 

case_collection_begin               5595 lines
execution_collection_decision      11217 lines
case_collection_summary             5595 lines

archive_created                     3501 lines
case_upload_attempt                 3507 lines
case_ingestion_attempt_completed    3507 lines
case_ingestion_request_failed          6 lines
case_ingestion_retry_completed      3501 lines
case_ingested                       3501 lines

(remaiing logfile summaries, etc)     22 lines

@tomvothecoder

Copy link
Copy Markdown
Collaborator Author

case_ingestion_request_failed 6 lines

Can get the names of the 6 cases that failed and their error codes? I wonder if the upload limit needs to be further increased.

@TonyB9000

TonyB9000 commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

I'll examine the log-file for details. They all appear to be "Bad Gateway" or "Gateway-Timeout" issues:

(base) [ac.bartoletti1@chrlogin1 operations]$ cat SBCS-archive-chrysalis-2026090* | grep case_ingestion_request_failed
2026-09-01 14:04:16,503 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=case_ingestion_request_failed attempt=1 case_path=/lcrc/group/e3sm/PERF_Chrysalis/OLD_PERF/2025-01/performance_archive_chrysalis_e3sm_2025_01_07_00_45_55/ac.kai.zhang/v3.LR.piClim-histaer error="HTTP 502: <html>\r\n<head><title>502 Bad Gateway</title></head>\r\n<body>\r\n<center><h1>502 Bad Gateway</h1></center>\r\n<hr><center>nginx</center>\r\n</body>\r\n</html>\r\n" retrying=true status_code=502 transient=true
2026-09-01 14:52:26,685 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=case_ingestion_request_failed attempt=1 case_path=/lcrc/group/e3sm/PERF_Chrysalis/OLD_PERF/2025-03/performance_archive_chrysalis_e3sm_2025_03_06_00_46_47/ac.ambradl/masscons.LR.nlmap1 error="URL error: [Errno 32] Broken pipe" retrying=true status_code=null transient=true
2026-09-01 16:33:01,785 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=case_ingestion_request_failed attempt=1 case_path=/lcrc/group/e3sm/PERF_Chrysalis/OLD_PERF/2025-05/performance_archive_chrysalis_e3sm_2025_05_17_00_46_48/ac.szhang/20250502.v3.LR.IGPRCPROG.TEST2.DATM.CPLHIST.chrysalis error="HTTP 502: <html>\r\n<head><title>502 Bad Gateway</title></head>\r\n<body>\r\n<center><h1>502 Bad Gateway</h1></center>\r\n<hr><center>nginx</center>\r\n</body>\r\n</html>\r\n" retrying=true status_code=502 transient=true
2026-09-01 16:33:08,394 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=case_ingestion_request_failed attempt=2 case_path=/lcrc/group/e3sm/PERF_Chrysalis/OLD_PERF/2025-05/performance_archive_chrysalis_e3sm_2025_05_17_00_46_48/ac.szhang/20250502.v3.LR.IGPRCPROG.TEST2.DATM.CPLHIST.chrysalis error="HTTP 504: <html>\r\n<head><title>504 Gateway Time-out</title></head>\r\n<body>\r\n<center><h1>504 Gateway Time-out</h1></center>\r\n<hr><center>nginx</center>\r\n</body>\r\n</html>\r\n" retrying=true status_code=504 transient=true
2026-09-01 16:56:20,444 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=case_ingestion_request_failed attempt=1 case_path=/lcrc/group/e3sm/PERF_Chrysalis/OLD_PERF/2025-06/performance_archive_chrysalis_e3sm_2025_06_12_00_47_54/ac.ambradl/v3.LR.F2010.ei error="HTTP 502: <html>\r\n<head><title>502 Bad Gateway</title></head>\r\n<body>\r\n<center><h1>502 Bad Gateway</h1></center>\r\n<hr><center>nginx</center>\r\n</body>\r\n</html>\r\n" retrying=true status_code=502 transient=true
2026-09-01 18:07:40,634 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=case_ingestion_request_failed attempt=1 case_path=/lcrc/group/e3sm/PERF_Chrysalis/OLD_PERF/2025-08/performance_archive_chrysalis_e3sm_2025_08_21_00_48_54/ac.zguo/sens1022_77 error="HTTP 502: <html>\r\n<head><title>502 Bad Gateway</title></head>\r\n<body>\r\n<center><h1>502 Bad Gateway</h1></center>\r\n<hr><center>nginx</center>\r\n</body>\r\n</html>\r\n" retrying=true status_code=502 transient=true

Additionally, out of these "archive_run" daily log-files, only the very first one had these errors:

-rw-rw-r--+ 1 ac.bartoletti1 simboard 14890804 Sep  1 19:42 SBCS-archive-chrysalis-20260901_184501.log
-rw-rw-r--+ 1 ac.bartoletti1 simboard  8383324 Sep  3 13:15 SBCS-archive-chrysalis-20260903_181001.log
-rw-rw-r--+ 1 ac.bartoletti1 simboard  8371093 Sep  4 13:12 SBCS-archive-chrysalis-20260904_181002.log
-rw-rw-r--+ 1 ac.bartoletti1 simboard  8407890 Sep  5 13:12 SBCS-archive-chrysalis-20260905_181001.log
-rw-rw-r--+ 1 ac.bartoletti1 simboard  8403618 Sep  6 13:12 SBCS-archive-chrysalis-20260906_181001.log
-rw-rw-r--+ 1 ac.bartoletti1 simboard  8418306 Sep  7 13:12 SBCS-archive-chrysalis-20260907_181001.log

Of course, the first one took a bit longer than the others: (6 hours, versus a few minutes).

(base) [ac.bartoletti1@chrlogin1 operations]$ for file in `ls SBCS-archive-chrysalis*`; do tail -1 $file; done
2026-09-01 19:42:53,913 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=run_finished mode=ingest scan_mode=archive exit_code=0 duration_seconds=21458.032
2026-09-03 13:15:32,135 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=run_finished mode=ingest scan_mode=archive exit_code=0 duration_seconds=314.382
2026-09-04 13:12:37,766 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=run_finished mode=ingest scan_mode=archive exit_code=0 duration_seconds=145.919
2026-09-05 13:12:28,479 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=run_finished mode=ingest scan_mode=archive exit_code=0 duration_seconds=135.358
2026-09-06 13:12:07,187 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=run_finished mode=ingest scan_mode=archive exit_code=0 duration_seconds=116.171
2026-09-07 13:12:10,536 [INFO]: archive_ingestor_core.py(_log_event:893) >> event=run_finished mode=ingest scan_mode=archive exit_code=0 duration_seconds=122.622

@TonyB9000

Copy link
Copy Markdown
Collaborator

Here are the summary reports generated daily from the output logs:

-rw-rw-r--+ 1 ac.bartoletti1 simboard        0 Sep  8 17:49 Staging_Errors-20260903
-rw-rw-r--+ 1 ac.bartoletti1 simboard        0 Sep  8 17:48 Staging_Errors-20260904
-rw-rw-r--+ 1 ac.bartoletti1 simboard        0 Sep  8 17:46 Staging_Errors-20260905
-rw-rw-r--+ 1 ac.bartoletti1 simboard        0 Sep  8 17:47 Staging_Errors-20260906
-rw-rw-r--+ 1 ac.bartoletti1 simboard        0 Sep  8 17:48 Staging_Errors-20260907
-rw-rw-r--+ 1 ac.bartoletti1 simboard      371 Sep  3 17:53 Staging_Gains-20260902
-rw-rw-r--+ 1 ac.bartoletti1 simboard     2067 Sep  3 19:10 Staging_Gains-20260903
-rw-rw-r--+ 1 ac.bartoletti1 simboard      332 Sep  4 19:10 Staging_Gains-20260904
-rw-rw-r--+ 1 ac.bartoletti1 simboard     1893 Sep  5 19:10 Staging_Gains-20260905
-rw-rw-r--+ 1 ac.bartoletti1 simboard      642 Sep  6 19:10 Staging_Gains-20260906
-rw-rw-r--+ 1 ac.bartoletti1 simboard        0 Sep  7 19:10 Staging_Gains-20260907
-rw-rw-r--+ 1 ac.bartoletti1 simboard      728 Sep  8 19:10 Staging_Gains-20260908

@TonyB9000

Copy link
Copy Markdown
Collaborator

Contents of latest "Staging Gains" file:

2026-09-08 03:07:47,677 case=performance_archive_chrysalis_e3sm_2026_09_08_02_57_39/ac.jwolfe/20260720.baseline.WCYCL2010.bluepulse.chrysalis accepted=1
2026-09-08 03:07:47,677 case=performance_archive_chrysalis_e3sm_2026_09_08_02_57_39/ac.jwolfe/v3.HR.1950_control accepted=1
2026-09-08 03:07:47,678 case=performance_archive_chrysalis_e3sm_2026_09_08_02_57_39/ac.xzheng/20260903.v3.LR.piControl-spinup_waterpond_cap-et-lakes.chrysalis accepted=1
2026-09-08 03:07:47,678 case=performance_archive_chrysalis_e3sm_2026_09_08_02_57_39/e3smtest/PFS.ne30pg2_r05_IcoswISC30E3r5.F2010.chrysalis_intel.bench-noio.C.JNPBench260907_231028 accepted=1
2026-09-08 10:21:46,451 case=ac.anolan/BCase.ne4_oQU240.WCYCL1850NS-OMEGA.gnu accepted=1

@tomvothecoder

tomvothecoder commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator Author

case_ingestion_request_failed 6 lines

Can get the names of the 6 cases that failed and their error codes? I wonder if the upload limit needs to be further increased.

@TonyB9000 I confirmed all five distinct cases were ingested successfully. The May case has two failed retry attempts, which is why the original log contained six failure entries.

Database Results

WITH wanted(case_path) AS (
  VALUES
    ('/lcrc/group/e3sm/PERF_Chrysalis/OLD_PERF/2025-01/performance_archive_chrysalis_e3sm_2025_01_07_00_45_55/ac.kai.zhang/v3.LR.piClim-histaer'),
    ('/lcrc/group/e3sm/PERF_Chrysalis/OLD_PERF/2025-03/performance_archive_chrysalis_e3sm_2025_03_06_00_46_47/ac.ambradl/masscons.LR.nlmap1'),
    ('/lcrc/group/e3sm/PERF_Chrysalis/OLD_PERF/2025-05/performance_archive_chrysalis_e3sm_2025_05_17_00_46_48/ac.szhang/20250502.v3.LR.IGPRCPROG.TEST2.DATM.CPLHIST.chrysalis'),
    ('/lcrc/group/e3sm/PERF_Chrysalis/OLD_PERF/2025-06/performance_archive_chrysalis_e3sm_2025_06_12_00_47_54/ac.ambradl/v3.LR.F2010.ei'),
    ('/lcrc/group/e3sm/PERF_Chrysalis/OLD_PERF/2025-08/performance_archive_chrysalis_e3sm_2025_08_21_00_48_54/ac.zguo/sens1022_77')
),
ingestion_summary AS (
  SELECT
    i.source_reference AS case_path,
    count(*) AS ingestion_records,
    max(i.created_at) AS last_ingested_at,
    array_agg(DISTINCT i.status::text) AS statuses,
    sum(i.created_count) AS executions_created,
    sum(i.duplicate_count) AS executions_duplicate,
    sum(i.error_count) AS ingestion_errors
  FROM ingestions i
  JOIN machines m ON m.id = i.machine_id
  JOIN wanted w ON w.case_path = i.source_reference
  WHERE m.name = 'chrysalis'
    AND i.source_type = 'hpc_upload'
  GROUP BY i.source_reference
)
SELECT
  w.case_path,
  COALESCE(s.ingestion_records, 0) AS ingestion_records,
  s.last_ingested_at,
  s.statuses,
  COALESCE(s.executions_created, 0) AS executions_created,
  COALESCE(s.executions_duplicate, 0) AS executions_duplicate,
  COALESCE(s.ingestion_errors, 0) AS ingestion_errors,
  CASE WHEN s.case_path IS NULL THEN 'not_ingested' ELSE 'ingested' END AS result
FROM wanted w
LEFT JOIN ingestion_summary s USING (case_path)
ORDER BY w.case_path;
Archive month Case Ingested at Created executions Status
2025-01 v3.LR.piClim-histaer 2026-09-01 12:04:18 -0700 1 success
2025-03 masscons.LR.nlmap1 2026-09-01 12:52:28 -0700 1 success
2025-05 20250502.v3.LR.IGPRCPROG.TEST2.DATM.CPLHIST.chrysalis 2026-09-01 14:33:11 -0700 1 success
2025-06 v3.LR.F2010.ei 2026-09-01 14:56:40 -0700 4 success
2025-08 sens1022_77 2026-09-01 16:08:30 -0700 1 success

Result: 5/5 cases ingested successfully; 8 executions created; no ingestion errors recorded.

Checking Upload Size

I also checked the upload sizes from /lcrc/group/e3sm2/simboard/operations/logs_raw_history.

Retries reuse the same prebuilt archive, so each case is listed once rather than summing retry sizes. All uploads were well below the configured 325 MiB NGINX request-body limit; upload size was therefore not the cause of the 502/504 responses. I think we can label these failures as transient network issues and deal with it later if it re-appears frequently. Subsequent retries should pick up these failures, as shown below (default is MAX_ATTEMPTS=3).

grep 'event=case_upload_attempt' SBCS-archive-chrysalis-2026090* \
  | grep -E \
'v3\.LR\.piClim-histaer|masscons\.LR\.nlmap1|20250502\.v3\.LR\.IGPRCPROG\.TEST2\.DATM\.CPLHIST\.chrysalis|v3\.LR\.F2010\.ei|sens1022_77' \
  | perl -ne 'print "$1\tattempt=$2\tupload_bytes=$3\n" if /case_path=([^ ]+).*attempt=(\d+).*archive_bytes=(\d+)/'
Failed case snapshot Failed attempt(s) Upload size
2025-01-07 v3.LR.piClim-histaer 1 4,454,584 bytes (4.25 MiB)
2025-03-06 masscons.LR.nlmap1 1 2,242,038 bytes (2.14 MiB)
2025-05-17 20250502...CPLHIST.chrysalis 1, 2 2,680,806 bytes (2.56 MiB)
2025-06-12 v3.LR.F2010.ei 1 10,526,162 bytes (10.04 MiB)
2025-08-21 sens1022_77 1 25,186,277 bytes (24.02 MiB)

@tomvothecoder

Copy link
Copy Markdown
Collaborator Author

@TonyB9000 This PR is in good shape. I think any additional logging improvements should be done in a new PR to prevent the scope of this PR from getting too large. We can also address the production cron jobs later in #304.

I will do a final cleanup and review. Let me know if there's anything else that needs to be updated first.

@tomvothecoder tomvothecoder left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

My initial self-review, then I will assign to agent to review.

Comment thread backend/app/scripts/ingestion/sites/chrysalis.config Outdated
Comment thread backend/app/scripts/ingestion/sites/chrysalis.config Outdated
Comment thread backend/app/scripts/ingestion/sites/crontab.example Outdated
Comment thread backend/app/scripts/ingestion/sites/crontab.example Outdated
Comment thread backend/app/scripts/ingestion/sites/Setup_for_Collection_Scripts_and_Crontab.md Outdated
@TonyB9000

TonyB9000 commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

@tomvothecoder I made some changes to log naming/location in the launch script. I don;t think that will be a problem. It is working correctly now, so I will push that soon.

@tomvothecoder

Copy link
Copy Markdown
Collaborator Author

@tomvothecoder I made some changes to log naming/location in the launch script. I don;t think that will be a problem. It is working correctly now, so I will push that soon.

Thanks. I just push a new commit: 807c119 (this PR) Can you review and update as needed?

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The scheduling and logging design has operational risks, and several documentation sections are incorrect or corrupted.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds config-driven HPC ingestion for Chrysalis while preserving NERSC path-based ingestion.

Changes:

  • Adds shared site launcher and Chrysalis/NERSC configurations.
  • Adds remote-state-aware dry runs and tests.
  • Expands ingestion, deployment, and database-access documentation.
File summaries
File Description
backend/app/scripts/README.md Documents operational ingestion workflows.
backend/app/scripts/ingestion/archive_ingestor_core.py Adds remote-state dry-run configuration.
backend/app/scripts/ingestion/archive_workflow.py Adjusts dry-run authentication validation.
backend/app/scripts/ingestion/hpc_upload_archive_ingestor.py Supports offline and remote-state dry runs.
backend/app/scripts/ingestion/nersc_archive_ingestor.py Supports offline and remote-state dry runs.
backend/app/scripts/ingestion/sites/chrysalis.config Defines Chrysalis ingestion settings.
backend/app/scripts/ingestion/sites/crontab.example Adds generic collection schedules.
backend/app/scripts/ingestion/sites/nersc.config Defines NERSC ingestion settings.
backend/app/scripts/ingestion/sites/nersc.crontab.example Removes superseded NERSC schedule.
backend/app/scripts/ingestion/sites/nersc.sh Removes superseded NERSC wrapper.
backend/app/scripts/ingestion/sites/site_ingestion_launcher.sh Adds the shared site launcher.
backend/tests/features/ingestion/test_archive_workflow.py Tests dry-run preconditions and logging.
backend/tests/features/ingestion/test_hpc_upload_archive_ingestor.py Tests upload-ingestor dry-run behavior.
backend/tests/features/ingestion/test_nersc_archive_ingestor.py Tests NERSC offline dry runs.
backend/tests/features/ingestion/test_site_collection_launcher.py Tests launcher configuration behavior.
docs/README.md Links database-access documentation.
docs/architecture/metadata-ingestion.md Documents dry-run and scan configuration.
docs/deploy/README.md Adds the database-access guide.
docs/deploy/nersc-spin-runbook.md References the replacement launcher.
docs/deploy/read-only-database-access.md Documents read-only PostgreSQL access.
docs/github-issues/154-ingestions/codex-implementation-plan.md Records the Chrysalis rollout plan.
docs/github-issues/154-ingestions/ingestion-sites-onboarding.md Adds site onboarding guidance.
docs/github-issues/154-ingestions/simboard-hpc-ingestion-architecture.png Adds an ingestion architecture diagram.
mkdocs.yml Adds the database guide to navigation.
Review details

Suppressed comments (9)

backend/app/scripts/README.md:37

  • The file tree omits the existing lcrc_v3_hpss_linker.py, even though the same README lists it as a primary entry point below. Keep this inventory consistent with the directory it documents.
│       ├── lcrc_v3.sh
│       └── lcrc_v3_archive_ingestor.py

backend/app/scripts/README.md:242

  • This heading labels the following section as the NERSC archive ingestor, but the content and command describe diagnostics_link_scanner.py. This makes the diagnostics operational instructions appear to belong to the wrong runner.
## NERSC Archive Ingestor

backend/app/scripts/README.md:268

  • These archive-ingestor settings do not apply to the diagnostics scanner described by this section; that scanner reads only MACHINE_NAME, DRY_RUN, and, for live runs, the API URL/token. The archive variable list and notes have displaced the scanner's LCRC invocation and operational behavior, so following this section will not configure the documented command correctly.
- `SCAN_MODE` (`staging` or `archive`, default `staging`)
- `PERF_ARCHIVE_ROOT` (default `/performance_archive` for `SCAN_MODE=staging`)
- `OLD_PERF_ARCHIVE_ROOT` (default `/OLD_PERF` for `SCAN_MODE=archive`)
- `MACHINE_NAME` (default `perlmutter`)
- `DRY_RUN` (default `true`)

docs/github-issues/154-ingestions/ingestion-sites-onboarding.md:56

  • This link points to a file that does not exist; the token guide is under docs/deploy/hpc-api-token-authentication.md.
See `docs/hpc_api_token_authentication.md` for service account and API token setup.

docs/github-issues/154-ingestions/ingestion-sites-onboarding.md:127

  • This reference repeats the nonexistent underscore-style path. Link to the deployed token guide instead.
- API token docs: `docs/hpc_api_token_authentication.md`

docs/github-issues/154-ingestions/ingestion-sites-onboarding.md:84

  • The new runners do not use a writable local state file; deduplication state and archive checkpoints are fetched from and persisted through the SimBoard API. This checklist item, and the state-file behavior wording at line 96, direct operators to validate a component that does not exist.
- The state file location is writable and persists across runs.

docs/github-issues/154-ingestions/codex-implementation-plan.md:52

  • The implementation has no STATE_PATH; state is database-backed and accessed through the ingestion-state and archive-checkpoint APIs. The same stale state-file model recurs at lines 73, 83, and 168, so the plan should describe remote state instead of asking site wrappers to configure a nonexistent path.
- set `STATE_PATH`

docs/github-issues/154-ingestions/codex-implementation-plan.md:152

  • The upload runner introduced here calls /ingestions/from-hpc-upload, not the browser-oriented /ingestions/from-upload endpoint. Listing the latter as sufficient gives the wrong API contract for the planned Chrysalis flow.
- Existing SimBoard `/ingestions/from-path` and `/ingestions/from-upload` APIs
  are sufficient for initial rollout planning.

backend/app/scripts/README.md:412

  • The sentence promises HPSS linker apply and verification commands, but the following content is an unrelated archive-ingestor environment list. Restore the linker commands so operators can actually perform the documented apply and idempotency check.
After review, apply the links and rerun the dry run to confirm idempotency:

- `SIMBOARD_API_BASE_URL`
  • Files reviewed: 23/24 changed files
  • Comments generated: 8
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread backend/app/scripts/ingestion/hpc_upload_archive_ingestor.py
Comment thread backend/app/scripts/ingestion/nersc_archive_ingestor.py
# Archive scan: run daily at 12:00 UTC. The site config supplies the default
# lower bound. Override `ARCHIVE_YEAR_START` or `ARCHIVE_YEAR_END` in the cron
# environment for a differently scoped scan. Values may use YYYY or YYYY-MM.
0 12 * * * cd "${SIMBOARD_ROOT}/operations" && "${SIMBOARD_ROOT}/repository/simboard/backend/app/scripts/ingestion/sites/site_ingestion_launcher.sh" chrysalis archive

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@tomvothecoder It occurs to me that we need a separate lock for the archive_run. It should not be paying any attention to the staging runs - but be fully independent. It did not occur to me that they may contend for network, etc (never tested that).

Comment on lines +91 to +94
ts="$(date -u +%Y%m%d_%H%M%S)"
LOG_FILE="${SIMBOARD_WORKDIR}/SBCS-${scan_mode}-${site}-${ts}.log"
printf '[%s] launcher started: site=%s scan_mode=%s dry_run=%s\n' \
"$(date -Is)" "${site}" "${scan_mode}" "${dry_run_normalized}" >> "${LOG_FILE}"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Address in #332

Comment thread backend/app/scripts/README.md
@@ -0,0 +1,20 @@
# Example crontab entries for a generic site collection launcher.
# Install with something like:
# crontab backend/app/scripts/ingestion/sites/<site>.crontab.example

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Address in #332

Comment thread docs/github-issues/154-ingestions/codex-implementation-plan.md Outdated
Comment thread docs/github-issues/154-ingestions/ingestion-sites-onboarding.md Outdated
@tomvothecoder

tomvothecoder commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator Author

@TonyB9000 Follow-up work is tracked in focused issues so this PR can merge as-is. I will pull the latest main in the simboard/repository/ directory and open separate branches for the follow-up work.

These are independent operational refinements; the Chrysalis ingestion functionality is in a good state for this PR to merge.

@tomvothecoder
tomvothecoder merged commit be7d3e7 into E3SM-Project:main Sep 10, 2026
1 check passed
@tomvothecoder
tomvothecoder deleted the feature/154-ingestion-sites branch September 10, 2026 21:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: devops DevOps task (e.g., DOE site ingestion, CI/CD, Docker)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[DevOps]: Explore simple cron usage for data collection on Chrysalis [DevOps]: Implement remote data collection job at Chrysalis for ingestion

3 participants