Skip to content

[DevOps] Discover stability parameters of performance archive directories (PACE) #192

Description

@TonyB9000

Summary

Find out how PACE treats/mistreats the performance archive directories

Details

  1. Find out how the performance archive is periodically cleaned (cron job?)
  2. Investigate PACE to determine if it moves/alters files in the performance archive.  2) is the higher priority item.

Additional Context

  1. Chrysalis PACE Script (wrapper to 2): https://github.com/E3SM-Project/E3SM_test_scripts/blob/master/jenkins/chrysalis_pace.sh
  2. PACE Archive script: https://github.com/E3SM-Project/E3SM_test_scripts/blob/master/util/pace_archive.sh
  3. Main Perl Ingestion Script: https://pace.ornl.gov/static/tools/e3sm_perf_archive.perl

Deliverables

Negotiated stability or standardization of PACE treatment of "performance archive" directories, sufficient to allow predictable ingestion by SimBoard backend.

  • Confirm whether the e3sm_perf_archive.perl script is still the active PACE archival mechanism.
    • This is an underlying processing script for a machine specific script at each site.
  • Figure out if the e3sm_perf_archive.perl script runs on a scheduled job
    • No, this is an underlying script that is wrapped by a machine specific bash script.
  • Figure out the frequency the machine specific bash scripts are ran
    • Seems like daily based on OLD_PERF_ARCHIVE_DIR directory structure.
  • Find output directory where timestamped performance archive is stored:
    • SimBoard should scan and ingest OLD_PERF_ARCHIVE_DIR with PERF_ARCHIVE_DIR.

Refer to https://e3sm.atlassian.net/wiki/spaces/EPG/pages/5477335106/PACE+Collection+and+Upload+Reference#Machine-Specific-Processing%2FUploading-Table for latest machine mapping table.

Dependencies & Blockers

Consultation with @youngsung Kim regarding PACE processing.

Due Date

2026-07-03

Additional Context

e3sm_perf_archive.perl script at NERSC with Perlmutter support
Source: /global/cfs/cdirs/e3sm/performance_archive/e3sm_perf_archive.perl.sarat

#!/usr/bin/perl

#  Perl 5 required.
require 5.004;

#  Strict syntax checking.
use strict;

#  Modules.
use Cwd;
use Time::Local;
use Sys::Hostname;
use Net::Domain qw(domainname hostdomain);

=head1 NAME

e3sm_perf_archive

=head1 SYNOPSIS

Move performance archive data for completed or failed
jobs into time-stamped directory for permanent archiving.

=head1 DESCRIPTION

If it does not already exist, create a directory with the name 

  performance_archive_<system>_<project_space_name>_<year>_<month>_<day>_<hour>_<min>_<sec>

Next, move performance data for completed or failed jobs 
into the new directory, preserving the same directory
structure (name/case name/job id). Uses system job monitoring
tools to identify which jobs are still running (so
that associated data are not moved).

Usage:

perl e3sm_perf_archive.perl

within the top level of a performance_archive.

=head1 AUTHOR

Pat Worley E<lt>F<worleyph@ornl.gov>E<gt>
Sarat Sreepathi E<lt>F<sarat@ornl.gov>E<gt>

=cut

###############################################################################
#
#  Miscellaneous global flags and variables.
#
###############################################################################

###############################################################################
#
#  This is the call to main.
#
###############################################################################

    &main();

exit 0;

###############################################################################
#
#  This is main.
#
###############################################################################

sub main {

  # Define variables.
  my $timestamp;
  my $i;
  my $arg_flag;
  my $host;
  my $domain;
  my $system;
  my $center;
  my $performance_archive;
  my @path;
  my $project_space;
  my $sec;
  my $min;
  my $hour;
  my $mday;
  my $mon;
  my $year;
  my $wday;
  my $yday;
  my $isdst;
  my $calyear;
  my $twodigitsec;
  my $twodigitmin;
  my $twodigithour;
  my $twodigitday;
  my $twodigitmonth;
  my $new_archive_name;
  my $new_archive_directory;
  my $mode;
  my @users;
  my $thisuser;
  my $new_user_directory;
  my $thisuser_directory;
  my @cases;
  my $thiscase;
  my $new_case_directory;
  my $thiscase_directory;
  my @jlids;
  my $thisjlid;
  my $thislid;
  my $thisjobid;
  my $thisjobid2;
  my $thisjlid_directory;
  my $thissystem;
  my $job_statsf_string;
  my $job_statsf;
  my $job_statsf_readable;
  my @squeueall_field;
  my $thisfeatures;
  my $thisexechost;
  my $thiscompleted;
  my $CaseStatusf;
  my @jlidfiles;
  my $thisjobstat;
  my $acme_timingf;
  my $e3sm_timingf;

  $timestamp = "unset";
  for (my $i=0; $i <= $#ARGV; $i+=2){
    $arg_flag = $ARGV[$i];
    if (($arg_flag =~ m/^-h$/) || ($arg_flag =~ m/^--help$/)){
      print
"Usage:

perl e3sm_perf_archive.perl

within the top level of a performance_archive at NERSC, 
at ALCF, at OLCF, on Anvil, on Chrysalis, or on Compy

Purpose:
Tool to aid in the permanent archiving of E3SM performance data.

If it does not already exist, create a directory with the name 

  performance_archive_<system>_<project_space_name>_<year>_<month>_<day>_<hour>_<min>_<sec>

Next, move performance data for completed or failed jobs 
into the new directory, preserving the same directory
structure (name/case name/job id). This uses system job monitoring
tools to identify which jobs are still running (so that
associated data are not moved). For centers with multiple systems, 
this tool will need to be run once on each system front end for
the given performance_archive directory if multiple
systems save performance data in the same directory.

Optional arguments:

  -h, --help      show this help message and exit 
  -t, --timestamp specify the timestamp (<year>_<month>_<day>_<hour>_<min>_<sec>)
                  to use in the name of the performance_archive directory. The
                  input is checked that it satisfies the general format, and aborts
                  otherwise. (Default is to use the script execution local time.) 
\n";
      exit;
    }elsif(($arg_flag =~ m/^--timestamp$/) || ($arg_flag =~ m/^-t$/)){
      $timestamp = $ARGV[++$i];
      if ($timestamp !~ m/^(\d+)\_(\d+)\_(\d+)\_(\d+)\_(\d+)\_(\d+)$/){
        print "Error: timestamp input (\"$timestamp\") does not match required format\n";
        exit;
      }
    }
  }

  $host = hostname;
  $domain = hostdomain;
  print $host;
  print $domain;

  if    ($host =~ m/^blueslogin\S*/){
    $system = "anvil";
    $center = "anvil";
  } 
  elsif ($host =~ m/^chrlogin\S*/){
    $system = "chrysalis";
    $center = "chrysalis";
  }
  elsif ($host =~ m/^compy\S*/){
    $system = "compy";
    $center = "compy";
  } 
  elsif ($host =~ m/^frontier\S*/){
    $system = "frontier";
    $center = "frontier";
  }
  elsif ($domain =~ m/\S*perlmutter\S*/){
    $system = "perlmutter";
    $center = "perlmutter";
  } 
  else{
    die "ERROR: Can only be used on a supported system: anvil, chrysalis, compy, perlmutter, frontier";
  }

# print "$system\n";

  $performance_archive = cwd();

# print "$performance_archive\n";

  if ($system =~ m/perlmutter/){
    if ($performance_archive =~ m/^\/global\/cfs\/cdirs\/\S*/){
      @path = split(/\//,$performance_archive);
      $project_space = $path[4];
    }else{
      die "ERROR: On Perlmutter, must be in a /global/cfs/cdirs/ subdirectory for this script to work";
      exit;
    }
  }elsif ($system =~ m/anvil/){
    if ($performance_archive =~ m/^\/lcrc\/group\/\S*/){
      @path = split(/\//,$performance_archive);
      $project_space = $path[3];
    }else{
      die "ERROR: On Anvil, must be in a /lcrc/group/ subdirectory for this script to work";
      exit;
    }
  }elsif ($system =~ m/chrysalis/){
    if ($performance_archive =~ m/^\/lcrc\/group\/\S*/){
      @path = split(/\//,$performance_archive);
      $project_space = $path[3];
    }else{
      die "ERROR: On Chrysalis, must be in a /lcrc/group/ subdirectory for this script to work";
      exit;
    }
  }elsif ($system =~ m/compy/){
    if ($performance_archive =~ m/^\/compyfs\/\S*/){
      $project_space = "all";
    }else{
      die "ERROR: On Compy, must be in a /compyfs/ subdirectory for this script to work";
      exit;
    }
  }elsif ($system =~ m/frontier/){
    if ($performance_archive =~ m/^\/lustre\/orion\/cli115\/world-shared\/\S*/){
      @path = split(/\//,$performance_archive);
      $project_space = $path[3];
    }else{
      die "ERROR: On Frontier, must be in a /lustre/orion/cli115/world-shared/ subdirectory for this script to work";
      exit;
    }
  }
# print "$project_space\n";

  if ($timestamp =~ m/unset/){
    ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
    $twodigitsec = sprintf("%02d", $sec);
    $twodigitmin = sprintf("%02d", $min);
    $twodigithour = sprintf("%02d", $hour);
    $twodigitday = sprintf("%02d", $mday);
    $twodigitmonth = sprintf("%02d", $mon+1);
    $calyear = $year + 1900;
    $timestamp = "$calyear\_$twodigitmonth\_$twodigitday\_$twodigithour\_$twodigitmin\_$twodigitsec";
  }
  $new_archive_name = "performance\_archive\_$center\_$project_space\_$timestamp";

  if (-e $new_archive_name){
    if (!(-d $new_archive_name)){
      die "ERROR: $new_archive_name must be a directory";
    }
  }else{
    mkdir "$new_archive_name";
  }

  $new_archive_directory = "$performance_archive/$new_archive_name";
  $mode = 0775;
  $mode = $mode | 02000;  # Set group sticky bit.
  chmod $mode, "$new_archive_directory";

  print "Creating/Using $new_archive_directory\n";

  opendir USERS, $performance_archive;
  @users = grep { -d $_ && -r _ && /^(?!\.).*$/ } readdir USERS;

  USERLOOP: for ( @users ){
    $thisuser = $_;
    if (!($thisuser =~ m/^performance\_archive\S*/)){

      print "USER: $thisuser\n";

      $new_user_directory = "$new_archive_directory/$thisuser";

      chdir "$performance_archive/$thisuser" || next USERLOOP;
      $thisuser_directory = cwd();

      opendir CASES, $thisuser_directory || next USERLOOP;
      @cases = grep { -d $_ && -r _ && /^(?!\.).*$/ } readdir CASES;

      CASELOOP: for ( @cases ){
        $thiscase = $_;

        print "  CASE: $thiscase\n";

        $new_case_directory = "$new_user_directory/$thiscase";

        chdir "$thisuser_directory/$thiscase" || next CASELOOP;
        $thiscase_directory = cwd();

        opendir JLIDS, $thiscase_directory || next CASELOOP;
        @jlids = grep { -d $_ && -r _ && /^(?!\.).*$/ } readdir JLIDS;

        JLIDSLOOP: for ( @jlids ){
          $thisjlid = $_;

          $thisjobid = 0;
          $thisjobid2 = 0;
          if ($thisjlid =~ m/^(\d+)\S*\.(\d+\-\d+)$/){
            $thisjobid = $1;
            $thislid   = $2;
          }

          print "    LID: $thislid JOBID: $thisjobid\n";

          chdir "$thiscase_directory/$thisjlid" || next JLIDSLOOP;
          $thisjlid_directory = cwd();
          # Verify job id from job_statsf file.
          if ($system =~ m/anvil|chrysalis/){
            $job_statsf_string = "squeueall_jobid";
            # For LCRC systems, get system name from job_statsf file.
            $thissystem = "unset";
          }elsif ($system =~ m/compy/){
            $job_statsf_string = "squeueall_jobid";
            # For non-NERSC and non-LCRC systems, already know system.
            $thissystem = $system;
          }elsif ($system =~ m/theta/){
            $job_statsf_string = "qstatf_jobid";
            # For non-NERSC and non-LCRC systems, already know system.
            $thissystem = $system;
          }elsif ($system =~ m/perlmutter/){
            $job_statsf_string = "squeues";
            # For NERSC, get system name from job_statsf file.
            $thissystem = "perlmutter";
          }elsif ($system =~ m/summit/){
            $job_statsf_string = "bjobslUF_jobid";
            # For non-NERSC and non-LCRC systems, already know system.
            $thissystem = $system;
          }
          $job_statsf = "$thisjlid_directory/$job_statsf_string.$thisjlid.gz";
          $job_statsf_readable = "false";

          if (-f $job_statsf && -r _ ){
            open(JOBSTATSF, "gunzip -c $job_statsf |") || die "can't open pipe to $job_statsf";
            $job_statsf_readable = "true";
          }else{
            $job_statsf = "$thisjlid_directory/$job_statsf_string.$thisjlid";
            if (-f $job_statsf && -r _ ){
              open(JOBSTATSF, "$job_statsf");
              $job_statsf_readable = "true";
            }
          }

          if ($job_statsf_readable =~ m/true/){

            if ($job_statsf_string =~ m/sqsf_jobid/){
              JOBFLOOP1: while (<JOBSTATSF>){
                if     (m/^JobId=(\d+)\s*JobName=(\S*)\s*$/){
                  $thisjobid2 = $1;
                  if ($thisjobid =~ m/0/){
                    $thisjobid = $thisjobid2;
                  }elsif (!($thisjobid =~ m/($thisjobid2)/)){
                    print "MISMATCHED JOB IDS: $thisjlid_directory\n";
                    print "  USER: $thisuser\n";
                    print "    CASE: $thiscase\n";
                    print "      JLID: $thisjlid\n";
                    print "        JOB IDS: $thisjobid $thisjobid2\n";
                    close JOBSTATSF;
                    next JLIDSLOOP;
                  }
                }elsif (m/^\s*Features=(\S+)\s+/){
                  # In the past the Cori systems and the Edison system used the
                  # same performance_archive directory. To differentiate on which
                  # system a given job ran, we used information in the job_statsf
                  # file. While Edison has been decommissioned, this issue may
                  # reoccur when the Perlmutter system comes on-line. We retain
                  # the following logic in case it becomes useful again.
                  $thisfeatures = $1;
                  if ($thisfeatures =~ m/haswell/){
                    $thissystem = "cori";
                  }
                  elsif ($thisfeatures =~ m/knl/){
                    $thissystem = "cori";
                  }
                  last JOBFLOOP1;
                }
              }
            }elsif ($job_statsf_string =~ m/squeueall_jobid/){
              JOBFLOOP2: while (<JOBSTATSF>){
                @squeueall_field = split(/\|/,$_);
                $thisjobid2 = $squeueall_field[8];
                if ($thisjobid2 =~ m/JOBID/){
                  next JOBFLOOP2;
                }
                if ($thisjobid =~ m/0/){
                  $thisjobid = $thisjobid2;
                }elsif (!($thisjobid =~ m/($thisjobid2)/)){
                  print "MISMATCHED JOB IDS: $thisjlid_directory\n";
                  print "  USER: $thisuser\n";
                  print "    CASE: $thiscase\n";
                  print "      JLID: $thisjlid\n";
                  print "        JOB IDS: $thisjobid $thisjobid2\n";
                  close JOBSTATSF;
                  next JLIDSLOOP;
                }
                # At one point Anvil and Chrysalis used the same performance_archive
                # directory. To differentiate on which system a given job ran, use
                # information in the job_statsf file.
                if ($system =~ m/anvil|chrysalis/){
                  $thisexechost = $squeueall_field[27];
                  if     ($thisexechost =~ m/b\S+/){
                    $thissystem = "anvil";
                  }elsif ($thisexechost =~ m/chr\S+/){
                    $thissystem = "chrysalis";
                  }
                }
                last JOBFLOOP2;
              }
            }elsif ($job_statsf_string =~ m/bjobslUF_jobid/){
              JOBFLOOP3: while (<JOBSTATSF>){
                if (m/^Job\s*<(\S+)?>.*$/){
                  $thisjobid2 = $1;
                  if ($thisjobid =~ m/0/){
                    $thisjobid = $thisjobid2;
                  }elsif (!($thisjobid =~ m/($thisjobid2)/)){
                    print "MISMATCHED JOB IDS: $thisjlid_directory\n";
                    print "  USER: $thisuser\n";
                    print "    CASE: $thiscase\n";
                    print "      JLID: $thisjlid\n";
                    print "        JOB IDS: $thisjobid $thisjobid2\n";
                    close JOBSTATSF;
                    next JLIDSLOOP;
                  }
                  last JOBFLOOP3;
                }
              }
            }else{
              JOBFLOOP4: while (<JOBSTATSF>){
                if ((m/^Job Id:\s*(\d+)\S*\s*$/) || 
                    (m/^JobID:\s*(\d+)\S*\s*$/)){
                  $thisjobid2 = $1;
                  if ($thisjobid =~ m/0/){
                    $thisjobid = $thisjobid2;
                  }elsif (!($thisjobid =~ m/($thisjobid2)/)){
                    print "MISMATCHED JOB IDS: $thisjlid_directory\n";
                    print "  USER: $thisuser\n";
                    print "    CASE: $thiscase\n";
                    print "      JLID: $thisjlid\n";
                    print "        JOB IDS: $thisjobid $thisjobid2\n";
                    close JOBSTATSF;
                    next JLIDSLOOP;
                  }
                  last JOBFLOOP4;
                }
              }
            }

            close JOBSTATSF;

          }

          if ($thissystem =~ m/unset/){
            # If system still not defined, check the acme_timing or e3sm_timing file (if available)
            $acme_timingf = "$thisjlid_directory/acme_timing.$thiscase.$thisjlid.gz";
            $e3sm_timingf = "$thisjlid_directory/e3sm_timing.$thiscase.$thisjlid.gz";
            if ((-f $acme_timingf) || (-f $e3sm_timingf)) {
              if (-f $acme_timingf) {
                open(TIMINGF, "gunzip -c $acme_timingf |") || die "can't open pipe to $acme_timingf";
              }else{
                open(TIMINGF, "gunzip -c $e3sm_timingf |") || die "can't open pipe to $e3sm_timingf";
              }

              TIMINGFLOOP: while (<TIMINGF>){
                if (m/^\s*Machine\s*:\s*(\S+)/){
                  $thissystem = $1;
                  last TIMINGFLOOP;
                }
              }

              close TIMINGF;
            }
          }

          if ($thissystem =~ m/unset/){
            print "POTENTIAL EARLY FAILURE: $thisjlid_directory\n";
            print "  USER: $thisuser\n";
            print "    CASE: $thiscase\n";
            print "      JLID: $thisjlid\n";
            next JLIDSLOOP;
          }

          print "      JOBID: $thisjobid JOBID2: $thisjobid2\n";
          print "      SYSTEM: $thissystem  SCRIPT-SYSTEM: $system\n";

          # If a gzipped version of CaseStatus exists, then job very likely completed.
          $thiscompleted = "false";
          $CaseStatusf = "CaseStatus.$thisjlid.gz";
          if (-f $CaseStatusf){
            $thiscompleted = "true";

            # But also check that all files are gzipped in this directory.
            opendir JLIDFILES, $thisjlid_directory;

            @jlidfiles = grep { -f $_ && -r _ && /^.*(?<!\.gz)$/ } readdir JLIDFILES;
            for ( @jlidfiles ){
              $thiscompleted = "false";
            }

          }

          print "        COMPLETED: $thiscompleted\n";

          # If not obviously completed, next check whether running, but should
          # by queried only on front end of system where the job ran.
          if (($thissystem =~ m/$system/) && ($thiscompleted =~ m/false/)){

            if ($system =~ m/anvil|chrysalis|compy/){
              $thisjobstat = `/usr/bin/squeue --job $thisjobid --noheader 2>&1`;
              if (($thisjobstat =~ m/^.*(slurm_load_jobs error: Invalid job id specified).*$/) ||
                  ($thisjobstat =~ m/^\s*$/)){
                $thiscompleted = "true";
              } else {
                print "RUNNING: $thisjlid_directory\n";
                print "  USER: $thisuser\n";
                print "    CASE: $thiscase\n";
                print "      JLID: $thisjlid\n";
              }
            }elsif ($system =~ m/cori/){
              $thisjobstat = `/usr/bin/squeue --job $thisjobid --noheader 2>&1`;
              if ($thisjobstat =~ m/^.*(slurm_load_jobs error: Invalid job id specified).*$/){
                $thiscompleted = "true";
              } else {
                print "RUNNING: $thisjlid_directory\n";
                print "  USER: $thisuser\n";
                print "    CASE: $thiscase\n";
                print "      JLID: $thisjlid\n";
	      }
            }elsif ($system =~ m/summit/){
              $thisjobstat = `bjobs $thisjobid 2>&1`;
              if ($thisjobstat =~ m/^Job <$thisjobid> is not found\s*$/){
                $thiscompleted = "true";
              } else {
                print "RUNNING: $thisjlid_directory\n";
                print "  USER: $thisuser\n";
                print "    CASE: $thiscase\n";
                print "      JLID: $thisjlid\n";
	      }
            }elsif ($system =~ m/theta/){
              $thisjobstat = `/usr/bin/qstat $thisjobid 2>&1`;
              if ($thisjobstat =~ m/^\s*$/){
                $thiscompleted = "true";
              } else {
                print "RUNNING: $thisjlid_directory\n";
                print "  USER: $thisuser\n";
                print "    CASE: $thiscase\n";
                print "      JLID: $thisjlid\n";
	      }
            }

            print "        COMPLETED2: $thiscompleted\n";

	  }

          # If completed, move data for this job to the new time-stamped performance 
          # archive. While not scrictly necessary, only allow script running
          # on the front end of a system to move data for jobs running on the
          # corresponding system.
          if (($thissystem =~ m/$system/) && ($thiscompleted =~ m/true/)){

             # First, check whether need to create a new user directory.
             if (-e $new_user_directory){
               if (!(-d $new_user_directory)){
                 die "ERROR: $new_user_directory must be a directory";
               }
             }else{
               mkdir "$new_user_directory";
               chmod $mode, "$new_user_directory";
             }

             # Next, check whether need to create a new case directory.
             if (-e $new_case_directory){
               if (!(-d $new_case_directory)){
                 die "ERROR: $new_case_directory must be a directory";
               }
             }else{
               mkdir "$new_case_directory";
               chmod $mode, "$new_case_directory";
             }

             # Finally, move the job directory.
             chdir "$thiscase_directory";
print "$thiscase_directory\n";
print "mv $thisjlid_directory $new_case_directory/$thisjlid`\n";
             `mv $thisjlid $new_case_directory/$thisjlid`

          }         

        }

        # Delete the case directory if it is now empty.
        rmdir "$thiscase_directory";

      }

      # Delete the user directory if it is now empty.
      rmdir "$thisuser_directory";

    }

  }

}

1;

__END__
What this script does

This script is a one-shot maintenance tool for archiving completed or failed E3SM performance jobs.

Step by step:

  1. Parses optional arguments:
    • -h / --help prints usage.
    • -t / --timestamp lets the caller provide the timestamp used in the archive directory name.
  2. Detects the current system from the hostname/domain:
    • anvil
    • chrysalis
    • compy
    • frontier
    • perlmutter
  3. Verifies that the script is being run from an expected filesystem path for that system.
  4. Determines the current performance_archive directory from cwd().
  5. Creates or reuses a timestamped archive directory named like:
    performance_archive_<system>_<project_space>_<year>_<month>_<day>_<hour>_<min>_<sec>
    
  6. Scans the current archive layout:
    <performance_archive>/<user>/<case>/<job_lid_directory>
    
  7. For each job/LID directory, it tries to determine:
    • job ID
    • LID
    • system where the job ran
    • whether the job is still running
  8. It checks job metadata files such as scheduler output files and timing files to verify the job ID and system.
  9. It treats a job as completed if:
    • CaseStatus.<jlid>.gz exists and all files in the job directory are gzipped, or
    • the scheduler no longer reports the job as running.
  10. If the job is completed/failed and belongs to the same system where the script is running, it moves the job directory into the timestamped archive while preserving the layout:
    <timestamped_archive>/<user>/<case>/<job_lid_directory>
    
  11. After processing a case, it tries to remove the original case directory:
    rmdir "$thiscase_directory";
    This only succeeds if the case directory is empty. If any job directories remain, the case directory is left in place.
  12. After processing a user, it also tries to remove the original user directory:
    rmdir "$thisuser_directory";
    This also only succeeds if the user directory is empty.

Summary: the script moves completed/failed job performance data into a timestamped archive directory and cleans up now-empty original case/user directories. It does not delete non-empty case directories, and it does not remove jobs that are still running or cannot be safely classified.

Example log output: /global/cfs/cdirs/e3sm/performance_archive_2026_04_15_15_43_10/process-perf-archive-2026_04_15_15_43_10.log

Metadata

Metadata

Assignees

No one assigned

    Labels

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

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions