# Long-Running Processes

> This alert finds Process Scheduler requests that are currently in Initiated or Processing status and have been running longer than their expected d...

---

Site [llms.txt](/llms.txt)

---

<div class="alert alert-warning border shadow-sm mb-4 psl-prospect-cta" role="note">
  <div class="d-flex flex-column flex-lg-row align-items-lg-center justify-content-between gap-3">
    <div>
      <strong>New to psLens?</strong> This page documents one specific alert. To see how it appears on the dashboard,
      what operators investigate, and how teams tune it, start with a live walkthrough.
    </div>
    <div class="d-flex flex-wrap gap-2">
      <a class="btn btn-sm btn-primary" href="/contact/">Book a Demo</a>
      <a class="btn btn-sm btn-outline-primary" href="/docs/alerts/">Browse Alerts</a>
    </div>
  </div>
</div><div id="pslens-context-panel" class="card border-info mb-4 d-none">
  <div class="card-header bg-light text-info py-2 fw-bold d-flex align-items-center border-bottom border-info-subtle">
    <i class="bi bi-info-circle-fill me-2"></i>
    <span>Tailored Operational Context</span>
  </div>
  <div class="card-body p-0">
    <ul class="list-group list-group-flush">
      <li id="row-db" class="list-group-item d-flex align-items-center justify-content-between py-2 d-none">
        <strong>Target Database:</strong>
        <span id="ctx-db" class="badge bg-secondary font-monospace">&mdash;</span>
      </li>
      <li id="row-type" class="list-group-item d-flex align-items-center justify-content-between py-2 d-none">
        <strong>Context Type:</strong>
        <span id="ctx-type" class="badge bg-light text-dark border font-monospace text-uppercase">&mdash;</span>
      </li>
      <li id="row-severity" class="list-group-item d-flex align-items-center justify-content-between py-2 d-none">
        <strong>Alert Severity:</strong>
        <span id="ctx-severity" class="badge">&mdash;</span>
      </li>
      <li id="row-time" class="list-group-item d-flex align-items-center justify-content-between py-2 d-none">
        <strong>Triggered Time:</strong>
        <span id="ctx-time" class="text-muted small">&mdash;</span>
      </li>
      <li id="row-details" class="list-group-item py-2 d-none">
        <strong id="label-details" class="d-block mb-1">Firing Context:</strong>
        <code id="ctx-details" class="d-block p-2 bg-light border rounded small"
          style="white-space: pre-wrap; word-break: break-all;">&mdash;</code>
      </li>
      <li id="row-sql" class="list-group-item py-2 d-none">
        <div class="d-flex justify-content-between align-items-center mb-1">
          <strong id="label-sql">Contextual SQL:</strong>
          <button id="btn-copy-sql" type="button" class="btn btn-sm btn-outline-secondary py-0 px-2 small">
            <i class="bi bi-clipboard me-1"></i>Copy SQL
          </button>
        </div>
        <pre class="mb-0 p-2 bg-light border rounded small"><code id="ctx-sql" style="white-space: pre-wrap; word-break: break-word;">&mdash;</code></pre>
      </li>
    </ul>
  </div>
</div>

<script>
  (function () {
    const params = new URLSearchParams(window.location.search);
    const metadata = params.get('metadata');
    if (!metadata) return;

    try {
      
      const base64 = metadata.replace(/-/g, '+').replace(/_/g, '/');
      const jsonStr = decodeURIComponent(escape(window.atob(base64)));
      const data = JSON.parse(jsonStr);

      if (data) {
        let hasData = false;

        if (data.db) {
          document.getElementById('ctx-db').textContent = data.db;
          document.getElementById('row-db').classList.remove('d-none');
          hasData = true;
        }

        if (data.type) {
          document.getElementById('ctx-type').textContent = data.type;
          document.getElementById('row-type').classList.remove('d-none');
          hasData = true;
        }

        if (data.severity) {
          const severityBadge = document.getElementById('ctx-severity');
          const severity = data.severity.toLowerCase();
          severityBadge.textContent = severity.toUpperCase();
          if (severity === 'critical') {
            severityBadge.className = 'badge bg-danger';
          } else if (severity === 'warning') {
            severityBadge.className = 'badge bg-warning text-dark';
          } else {
            severityBadge.className = 'badge bg-info';
          }
          document.getElementById('row-severity').classList.remove('d-none');
          hasData = true;
        }

        if (data.t) {
          const date = new Date(data.t * 1000);
          document.getElementById('ctx-time').textContent = date.toLocaleString();
          document.getElementById('row-time').classList.remove('d-none');
          hasData = true;
        }

        if (data.details) {
          document.getElementById('ctx-details').textContent = data.details;

          
          const labelDetails = document.getElementById('label-details');
          if (data.type === 'object') {
            labelDetails.textContent = 'Object Metadata Details:';
          } else if (data.type === 'report') {
            labelDetails.textContent = 'Report Description:';
          } else {
            labelDetails.textContent = 'Firing Context:';
          }

          document.getElementById('row-details').classList.remove('d-none');
          hasData = true;
        }

        if (data.sql) {
          document.getElementById('ctx-sql').textContent = data.sql;
          document.getElementById('row-sql').classList.remove('d-none');
          const copyBtn = document.getElementById('btn-copy-sql');
          if (copyBtn && navigator.clipboard) {
            copyBtn.addEventListener('click', function () {
              navigator.clipboard.writeText(data.sql).then(function () {
                copyBtn.innerHTML = '<i class="bi bi-check2 me-1"></i>Copied';
                setTimeout(function () {
                  copyBtn.innerHTML = '<i class="bi bi-clipboard me-1"></i>Copy SQL';
                }, 2000);
              });
            });
          }
          hasData = true;
        }

        if (hasData) {
          document.getElementById('pslens-context-panel').classList.remove('d-none');
        }
      }
    } catch (e) {
      console.error('Failed to parse operational context metadata:', e);
    }
  })();
</script>

## Long-Running Processes Alert

**Alert ID:** `long_running_processes`
**Category:** Process Scheduler
**Default threshold:** Dynamic (4x rolling median, min 10 min baseline) or static fallback (20 minutes)

### What This Alert Detects

This alert finds Process Scheduler requests that are currently in **Initiated** or **Processing** status and have been running longer than their expected duration.

Instead of relying solely on a static global threshold, `psLens` dynamically calculates an expected duration for each process using its own history:
- It fetches the last 10 successful run durations for the specific process name (`PRCSNAME`).
- It calculates the rolling median runtime of those successful runs.
- It raises an alert if the current execution duration exceeds a multiple of the median (default: **$4\times$ the median**, with a minimum baseline of **10 minutes**).
- If no successful runs are found (e.g. a brand new process or a process that has never completed successfully), it falls back to the static global `thresholdMinutes` (default: **20 minutes**).

A process that has been running for a long time may be stuck, consuming excessive server resources, or waiting on a lock or resource that will never become available.

### Severity Logic

|               Condition                | Severity |
| -------------------------------------- | -------- |
| Running longer than dynamic threshold (or static fallback) | Warning  |

For example:
- If a process usually runs in 3 minutes: median is 3m. $4\times 3 = 12$ minutes. The alert triggers if it runs for 12 minutes or more.
- If a process usually runs in 1 minute: median is 1m. $4\times 1 = 4$ minutes. Since this is below the 10-minute minimum baseline, the baseline is used: the alert triggers if it runs for 10 minutes or more.
- If a process has no history: fallback is used. The alert triggers if it runs for 20 minutes or more.

### What Gets Checked

The alert queries the Process Scheduler request table for processes in run status **6 (Initiated)** or **7 (Processing)**. For each result, it calculates how long the process has been running based on its `BeginDttm` (begin datetime) and the current server time.

Processes with no `BeginDttm` value are skipped (the process hasn't truly started yet).

### Alert Details

Each alert item includes:

- Process name (`PRCSNAME`)
- Process instance number
- How long the process has been running (in minutes)
- The rolling median runtime (if available, in minutes)
- The operator who submitted the request
- A link to the Process Monitor detail page for that instance

### Configuration

```yaml
alerts:
  checks:
    long_running_processes:
      enabled: true
      thresholdMinutes: 20            # Fallback static minutes when history is empty
      anomalyMultiplier: 4.0          # Multiplier applied to rolling median
      anomalyMinBaselineMinutes: 10   # Minimum baseline runtime before alerting
      excludeProcesses:               # Process names to skip
        - SOME_LONG_BATCH_JOB
```

|      Setting       | Default |                                                          Description                                                          |
| ------------------ | ------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `thresholdMinutes` | `20`    | Fallback static minutes a process must be running to trigger an alert if no successful run history exists.                     |
| `anomalyMultiplier`| `4.0`   | Multiplier applied to the rolling median duration to calculate the dynamic threshold.                                         |
| `anomalyMinBaselineMinutes` | `10` | The minimum baseline duration in minutes. Dynamic thresholds are capped to be at least this value to prevent false alerts on very fast processes. |
| `excludeProcesses` | `[]`    | List of process names to exclude from this check. Use for known long-running processes that are expected to take a long time. |

### How to Respond

1. Click the alert link to go directly to the Process Monitor entry for the flagged process
2. Review the process details: what it is, who submitted it, when it started
3. Check whether the process appears to be making progress or is stuck
4. If the process is genuinely stuck, you may need to cancel it from PeopleSoft's Process Monitor
5. Investigate why it got stuck: look for locks, resource contention, or data issues

### Tuning the Threshold

The right threshold depends on your environment. You can adjust `anomalyMultiplier` or `anomalyMinBaselineMinutes` globally or per-database to reduce noise, or use `excludeProcesses` to ignore specific jobs entirely.
