Repository: troycomi/reportseff

Issue:
Wrong memory efficiency when using "srun"
Hello,

probably related to #37, but a bit different, so I thought I'd open a new issue.

When I run:
`srun -n 8 stress -m 1 -t 52 --vm-keep --vm-bytes 1800M
`
I use 8 CPUs and almost 16GB, but `reportseff` gets the CPU efficiency OK, but the memory efficiency way off (it basically reports I only used 1800M).


```
$ seff 131042
######################## JOB EFFICIENCY REPORT ########################
# Job ID:          131042
# State:           COMPLETED (exit code 0)
# Cores:           8
# CPU Utilized:    00:06:58
# CPU Efficiency:  98.58% of 00:07:04 core-walltime
# Wall-clock time: 00:00:53
# Memory Utilized: 14.86 GB (estimated maximum)
#######################################################################

$ reportseff --debug 131042
^|^8^|^00:00:53^|^131042^|^131042^|^^|^1^|^16000M^|^COMPLETED^|^00:01:00^|^06:57.815
^|^8^|^00:00:53^|^131042.batch^|^131042.batch^|^20264K^|^1^|^^|^COMPLETED^|^^|^00:00.034
^|^8^|^00:00:53^|^131042.extern^|^131042.extern^|^1052K^|^1^|^^|^COMPLETED^|^^|^00:00.001
^|^8^|^00:00:53^|^131042.0^|^131042.0^|^1947276K^|^1^|^^|^COMPLETED^|^^|^06:57.779

   JobID    State       Elapsed  TimeEff   CPUEff   MemEff 
  131042  COMPLETED    00:00:53   88.3%    98.3%    11.9% 

```

Comments:

Patch:
diff --git a/pyproject.toml b/pyproject.toml
index d536f8b..2b81226 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "reportseff"
-version = "2.7.5"
+version = "2.7.6"
 description= "Tablular seff output"
 authors = ["Troy Comi <troycomi@gmail.com>"]
 license = "MIT"
diff --git a/src/reportseff/job.py b/src/reportseff/job.py
index 5cba205..43750f8 100644
--- a/src/reportseff/job.py
+++ b/src/reportseff/job.py
@@ -60,7 +60,6 @@ class Job:
         self.time: Optional[str] = "---"
         self.time_eff: Union[str, float] = "---"
         self.cpu: Optional[Union[str, float]] = "---"
-        self.mem: Union[str, float] = "---"
         self.state: Optional[str] = None
         self.mem_eff: Optional[float] = None
         self.gpu: Optional[float] = None
@@ -116,7 +115,8 @@ class Job:
                 if k not in self.other_entries or not self.other_entries[k]:
                     self.other_entries[k] = value
             mem = parsemem(entry["MaxRSS"]) if "MaxRSS" in entry else 0
-            self.stepmem = max(self.stepmem, mem)
+            tasks = int(entry.get("NTasks", 1))
+            self.stepmem = max(self.stepmem, mem * tasks)
 
             if "TRESUsageOutAve" in entry:
                 self.energy = max(
diff --git a/src/reportseff/output_renderer.py b/src/reportseff/output_renderer.py
index 161accd..a5c2da0 100644
--- a/src/reportseff/output_renderer.py
+++ b/src/reportseff/output_renderer.py
@@ -45,7 +45,7 @@ class OutputRenderer:
         # values derived from other values, list includes all dependent values
         self.derived: Dict[str, List] = {
             "CPUEff": ["TotalCPU", "AllocCPUS", "Elapsed"],
-            "MemEff": ["REQMEM", "NNodes", "AllocCPUS", "MaxRSS"],
+            "MemEff": ["REQMEM", "NNodes", "AllocCPUS", "MaxRSS", "NTasks"],
             "TimeEff": ["Elapsed", "Timelimit"],
             "GPU": [],
             "GPUMem": [],


Test patch:
diff --git a/conftest.py b/conftest.py
index 44f8a6a..0756750 100644
--- a/conftest.py
+++ b/conftest.py
@@ -30,6 +30,7 @@ def to_sacct_dict(sacct_line: str) -> dict:
         "State",
         "Timelimit",
         "TotalCPU",
+        "NTasks",
     )
     return dict(zip(columns, sacct_line.split("|")))
 
@@ -316,3 +317,90 @@ def multinode_job():
             "|36|12-14:16:39|6196869.batch|6196869.batch|33824748K|1|191846Mn|COMPLETED||451-06:00:24"
         ),
     ]
+
+
+@pytest.fixture
+def issue_41():
+    """job run on multiple nodes, with multiple tasks."""
+    return [
+        to_sacct_dict(
+            "|8|00:00:53|131042|131042||1|16000M|COMPLETED|00:01:00|06:57.815|8"
+        ),
+        to_sacct_dict(
+            "|8|00:00:53|131042.batch|131042.batch|20264K|1||COMPLETED||00:00.034|8"
+        ),
+        to_sacct_dict(
+            "|8|00:00:53|131042.extern|131042.extern|1052K|1||COMPLETED||00:00.001|8"
+        ),
+        to_sacct_dict(
+            "|8|00:00:53|131042.0|131042.0|1947276K|1||COMPLETED||06:57.779|8"
+        ),
+    ]
+
+
+@pytest.fixture
+def console_jobs():
+    """collection of sacct outputs for test_reportseff."""
+
+    # indexed on job id
+    return {
+        "25569410_notime": (
+            "^|^1^|^21:14:48^|^25569410^|^25569410^|^^|^1^|^1^|^4000Mc^|^"
+            "COMPLETED^|^19:28:36\n"
+            "^|^1^|^21:14:49^|^25569410.extern^|^25569410.extern^|^1548K^|^"
+            "1^|^1^|^4000Mc^|^COMPLETED^|^00:00:00\n"
+            "^|^1^|^21:14:43^|^25569410.0^|^25569410.0^|^62328K"
+            "^|^1^|^1^|^4000Mc^|^COMPLETED^|^19:28:36\n"
+        ),
+        "24418435_notime": (
+            "^|^1^|^01:27:42^|^24418435^|^24418435^|^^|^1^|^1^|^1Gn^|^"
+            "COMPLETED^|^01:27:29\n"
+            "^|^1^|^01:27:42^|^24418435.batch^|^24418435.batch^|^499092K^|^"
+            "1^|^1^|^1Gn^|^COMPLETED^|^01:27:29\n"
+            "^|^1^|^01:27:42^|^24418435.extern^|^24418435.extern^|^1376K^|^"
+            "1^|^1^|^1Gn^|^COMPLETED^|^00:00:00\n"
+        ),
+        "24418435": (
+            "^|^1^|^01:27:42^|^24418435^|^24418435^|^^|^1^|^1^|^1Gn^|^"
+            "COMPLETED^|^03:00:00^|^01:27:29\n"
+            "^|^1^|^01:27:42^|^24418435.batch^|^24418435.batch^|^499092K^|^"
+            "1^|^1^|^1Gn^|^COMPLETED^|^^|^01:27:29\n"
+            "^|^1^|^01:27:42^|^24418435.extern^|^24418435.extern^|^1376K^|^"
+            "1^|^1^|^1Gn^|^COMPLETED^|^^|^00:00:00\n"
+        ),
+        "23000233": (
+            "^|^16^|^00:00:00^|^23000233^|^23000233^|^^|^1^|^1^|^4000Mc^|^"
+            "CANCELLED by 129319^|^6-00:00:00^|^00:00:00\n"
+        ),
+        "24221219": (
+            "^|^1^|^00:09:34^|^24220929_421^|^24221219^|^^|^1^|^1^|^16000Mn^|^"
+            "COMPLETED^|^09:28.052\n"
+            "^|^1^|^00:09:34^|^24220929_421.batch^|^24221219.batch"
+            "^|^5664932K^|^1^|^1^|^16000Mn^|^COMPLETED^|^09:28.051\n"
+            "^|^1^|^00:09:34^|^24220929_421.extern^|^24221219.extern"
+            "^|^1404K^|^1^|^1^|^16000Mn^|^COMPLETED^|^00:00:00\n"
+        ),
+        "24221220": (
+            "^|^1^|^00:09:33^|^24220929_431^|^24221220^|^^|^1^|^1^|^16000Mn^|^"
+            "PENDING^|^09:27.460\n"
+            "^|^1^|^00:09:33^|^24220929_431.batch^|^24221220.batch"
+            "^|^5518572K^|^1^|^1^|^16000Mn^|^PENDING^|^09:27.459\n"
+            "^|^1^|^00:09:33^|^24220929_431.extern^|^24221220.extern"
+            "^|^1400K^|^1^|^1^|^16000Mn^|^PENDING^|^00:00:00\n"
+        ),
+        "23000381": (
+            "^|^8^|^00:00:12^|^23000381^|^23000381^|^^|^1^|^1^|^4000Mc^|^FAILED^|^00:00:00\n"
+            "^|^8^|^00:00:12^|^23000381.batch^|^23000381.batch^|^^|^1^|^1^|^4000Mc^|^"
+            "FAILED^|^00:00:00\n"
+            "^|^8^|^00:00:12^|^23000381.extern^|^23000381.extern^|^1592K^|^1^|^1^|^4000Mc^|^"
+            "COMPLETED^|^00:00:00\n"
+        ),
+        "23000210": (
+            "^|^8^|^00:00:00^|^23000210^|^23000210^|^^|^1^|^1^|^20000Mn^|^"
+            "FAILED^|^00:00.007\n"
+            "^|^8^|^00:00:00^|^23000210.batch^|^23000210.batch^|^1988K^|^1^|^1^|^20000Mn^|^"
+            "FAILED^|^00:00.006\n"
+            "^|^8^|^00:00:00^|^23000210.extern^|^23000210.extern^|^1556K^|^1^|^1^|^20000Mn^|^"
+            "COMPLETED^|^00:00:00\n"
+        ),
+    }
diff --git a/tests/test_job.py b/tests/test_job.py
index bb09c5e..7f6eb46 100644
--- a/tests/test_job.py
+++ b/tests/test_job.py
@@ -40,7 +40,6 @@ def test_job_init(job):
     assert job.totalmem is None
     assert job.time == "---"
     assert job.cpu == "---"
-    assert job.mem == "---"
     assert job.state is None
 
 
@@ -250,7 +249,7 @@ def test_update_part_job():
             "Elapsed": "00:10:00",
             "MaxRSS": "495644K",
             "NNodes": "1",
-            "NTasks": "",
+            "NTasks": "1",
         }
     )
     assert job.state is None
@@ -765,3 +764,16 @@ def test_multinode_job(multinode_job):
         job.update(line)
 
     assert job.cpu == 5.0
+
+
+def test_multinode_job_issue_41(issue_41):
+    """Testing issue 41 where multiple tasks are used.
+
+    Previously reported incorrect memory efficiency.
+    """
+    job = job_module.Job("131042", "131042", None)
+    for line in issue_41:
+        job.update(line)
+
+    assert job.cpu == 98.3
+    assert job.get_entry("MemEff") == 95.1
diff --git a/tests/test_output_renderer.py b/tests/test_output_renderer.py
index 763fe65..09896a6 100644
--- a/tests/test_output_renderer.py
+++ b/tests/test_output_renderer.py
@@ -175,7 +175,7 @@ def test_renderer_init(renderer):
     assert sorted(renderer.query_columns) == sorted(
         (
             "JobID JobIDRaw State Elapsed TotalCPU "
-            "AllocCPUS REQMEM NNodes MaxRSS AdminComment"
+            "AllocCPUS REQMEM NNodes NTasks MaxRSS AdminComment"
         ).split()
     )
 
@@ -277,7 +277,7 @@ def test_renderer_correct_columns(renderer):
         (
             "JobID TotalCPU Elapsed REQMEM"
             " JobIDRaw State AdminComment"
-            " NNodes AllocCPUS MaxRSS Timelimit"
+            " NNodes NTasks AllocCPUS MaxRSS Timelimit"
         ).split()
     )
 
diff --git a/tests/test_reportseff.py b/tests/test_reportseff.py
index a3a44d8..50a1f15 100644
--- a/tests/test_reportseff.py
+++ b/tests/test_reportseff.py
@@ -30,20 +30,13 @@ def mock_inquirer(mocker):
     )
 
 
-def test_directory_input(mocker, mock_inquirer):
+def test_directory_input(mocker, mock_inquirer, console_jobs):
     """Able to get jobs from directory calls."""
     mocker.patch("reportseff.console.which", return_value=True)
     runner = CliRunner()
     sub_result = mocker.MagicMock()
     sub_result.returncode = 0
-    sub_result.stdout = (
-        "^|^1^|^01:27:42^|^24418435^|^24418435^|^^|^1^|^1Gn^|^"
-        "COMPLETED^|^03:00:00^|^01:27:29\n"
-        "^|^1^|^01:27:42^|^24418435.batch^|^24418435.batch^|^499092K^|^1^|^1Gn^|^"
-        "COMPLETED^|^^|^01:27:29\n"
-        "^|^1^|^01:27:42^|^24418435.extern^|^24418435.extern^|^1376K^|^1^|^1Gn^|^"
-        "COMPLETED^|^^|^00:00:00\n"
-    )
+    sub_result.stdout = console_jobs["24418435"]
     mocker.patch("reportseff.db_inquirer.subprocess.run", return_value=sub_result)
 
     def set_jobs(self, directory):
@@ -68,20 +61,13 @@ def test_directory_input(mocker, mock_inquirer):
     ]
 
 
-def test_directory_input_exception(mocker, mock_inquirer):
+def test_directory_input_exception(mocker, mock_inquirer, console_jobs):
     """Catch exceptions in setting jobs from directory."""
     mocker.patch("reportseff.console.which", return_value=True)
     runner = CliRunner()
     sub_result = mocker.MagicMock()
     sub_result.returncode = 0
-    sub_result.stdout = (
-        "24418435^|^24418435^|^COMPLETED^|^1^|^"
-        "01:27:29^|^01:27:42^|^03:00:00^|^1Gn^|^^|^1^|^\n"
-        "24418435.batch^|^24418435.batch^|^COMPLETED^|^1^|^"
-        "01:27:29^|^01:27:42^|^^|^1Gn^|^499092K^|^1^|^1\n"
-        "24418435.extern^|^24418435.extern^|^COMPLETED^|^1^|^"
-        "00:00:00^|^01:27:42^|^^|^1Gn^|^1376K^|^1^|^1\n"
-    )
+    sub_result.stdout = console_jobs["24418435"]
     mocker.patch("reportseff.db_inquirer.subprocess.run", return_value=sub_result)
 
     def set_jobs(self, directory):
@@ -94,16 +80,13 @@ def test_directory_input_exception(mocker, mock_inquirer):
     assert "Testing EXCEPTION" in result.output
 
 
-def test_debug_option(mocker, mock_inquirer):
+def test_debug_option(mocker, mock_inquirer, console_jobs):
     """Setting debug prints subprocess result."""
     mocker.patch("reportseff.console.which", return_value=True)
     runner = CliRunner()
     sub_result = mocker.MagicMock()
     sub_result.returncode = 0
-    sub_result.stdout = (
-        "^|^16^|^00:00:00^|^23000233^|^23000233^|^^|^1^|^4000Mc^|^"
-        "CANCELLED by 129319^|^6-00:00:00^|^00:00:00\n"
-    )
+    sub_result.stdout = console_jobs["23000233"]
     mocker.patch("reportseff.db_inquirer.subprocess.run", return_value=sub_result)
     result = runner.invoke(
         console.main,
@@ -113,10 +96,7 @@ def test_debug_option(mocker, mock_inquirer):
     assert result.exit_code == 0
     # remove header
     output = result.output.split("\n")
-    assert output[0] == (
-        "^|^16^|^00:00:00^|^23000233^|^23000233^|^^|^1^|^4000Mc^|^"
-        "CANCELLED by 129319^|^6-00:00:00^|^00:00:00"
-    )
+    assert output[0] == console_jobs["23000233"].strip("\n")
     assert output[3].split() == [
         "23000233",
         "CANCELLED",
@@ -127,16 +107,13 @@ def test_debug_option(mocker, mock_inquirer):
     ]
 
 
-def test_process_failure(mocker, mock_inquirer):
+def test_process_failure(mocker, mock_inquirer, console_jobs):
     """Catch exceptions in process_entry by printing the offending entry."""
     mocker.patch("reportseff.console.which", return_value=True)
     runner = CliRunner()
     sub_result = mocker.MagicMock()
     sub_result.returncode = 0
-    sub_result.stdout = (
-        "^|^16^|^00:00:00^|^23000233^|^23000233^|^^|^1^|^4000Mc^|^"
-        "CANCELLED by 129319^|^6-00:00:00^|^00:00:00\n"
-    )
+    sub_result.stdout = console_jobs["23000233"]
     mocker.patch("reportseff.db_inquirer.subprocess.run", return_value=sub_result)
     mocker.patch.object(
         JobCollection, "process_entry", side_effect=Exception("TESTING")
@@ -153,21 +130,18 @@ def test_process_failure(mocker, mock_inquirer):
         "{'AdminComment': '', 'AllocCPUS': '16', "
         "'Elapsed': '00:00:00', 'JobID': '23000233', "
         "'JobIDRaw': '23000233', 'MaxRSS': '', 'NNodes': '1', "
-        "'REQMEM': '4000Mc', 'State': 'CANCELLED by 129319', "
+        "'NTasks': '1', 'REQMEM': '4000Mc', 'State': 'CANCELLED by 129319', "
         "'TotalCPU': '6-00:00:00'}"
     )
 
 
-def test_short_output(mocker, mock_inquirer):
+def test_short_output(mocker, mock_inquirer, console_jobs):
     """Outputs with 20 or fewer entries are directly printed."""
     mocker.patch("reportseff.console.which", return_value=True)
     runner = CliRunner()
     sub_result = mocker.MagicMock()
     sub_result.returncode = 0
-    sub_result.stdout = (
-        "^|^23000233^|^23000233^|^CANCELLED by 129319^|^16^|^"
-        "00:00:00^|^00:00:00^|^6-00:00:00^|^4000Mc^|^^|^1^|^\n"
-    )
+    sub_result.stdout = console_jobs["23000233"]
     mocker.patch("reportseff.db_inquirer.subprocess.run", return_value=sub_result)
     mocker.patch("reportseff.console.len", return_value=20)
     mocker.patch.object(OutputRenderer, "format_jobs", return_value="output")
@@ -179,16 +153,13 @@ def test_short_output(mocker, mock_inquirer):
     mock_click.assert_called_once_with("output", color=None)
 
 
-def test_long_output(mocker, mock_inquirer):
+def test_long_output(mocker, mock_inquirer, console_jobs):
     """Outputs with more than 20 entries are echoed via pager."""
     mocker.patch("reportseff.console.which", return_value=True)
     runner = CliRunner()
     sub_result = mocker.MagicMock()
     sub_result.returncode = 0
-    sub_result.stdout = (
-        "^|^16^|^00:00:00^|^23000233^|^23000233"
-        "^|^^|^1^|^4000Mc^|^CANCELLED by 129319^|^00:00:00\n"
-    )
+    sub_result.stdout = console_jobs["23000233"]
     mocker.patch("reportseff.db_inquirer.subprocess.run", return_value=sub_result)
     mocker.patch("reportseff.console.len", return_value=21)
     mocker.patch.object(OutputRenderer, "format_jobs", return_value="output")
@@ -199,20 +170,13 @@ def test_long_output(mocker, mock_inquirer):
     mock_click.assert_called_once_with("output", color=None)
 
 
-def test_simple_job(mocker, mock_inquirer):
+def test_simple_job(mocker, mock_inquirer, console_jobs):
     """Can get efficiency from a single job."""
     mocker.patch("reportseff.console.which", return_value=True)
     runner = CliRunner()
     sub_result = mocker.MagicMock()
     sub_result.returncode = 0
-    sub_result.stdout = (
-        "^|^1^|^01:27:42^|^24418435^|^24418435^|^^|^1^|^1Gn^|^"
-        "COMPLETED^|^01:27:29\n"
-        "^|^1^|^01:27:42^|^24418435.batch^|^24418435.batch^|^499092K^|^1^|^1Gn^|^"
-        "COMPLETED^|^01:27:29\n"
-        "^|^1^|^01:27:42^|^24418435.extern^|^24418435.extern^|^1376K^|^1^|^1Gn^|^"
-        "COMPLETED^|^00:00:00\n"
-    )
+    sub_result.stdout = console_jobs["24418435_notime"]
     mocker.patch("reportseff.db_inquirer.subprocess.run", return_value=sub_result)
     result = runner.invoke(
         console.main,
@@ -225,24 +189,14 @@ def test_simple_job(mocker, mock_inquirer):
     assert output[0].split() == ["24418435", "COMPLETED", "01:27:42", "99.8%", "47.6%"]
 
 
-def test_simple_user(mocker, mock_inquirer):
+def test_simple_user(mocker, mock_inquirer, console_jobs):
     """Can limit outputs by user."""
     mocker.patch("reportseff.console.which", return_value=True)
     runner = CliRunner()
     sub_result = mocker.MagicMock()
     sub_result.returncode = 0
     sub_result.stdout = (
-        "^|^1^|^01:27:42^|^24418435^|^24418435^|^^|^1^|^1Gn^|^"
-        "COMPLETED^|^01:27:29\n"
-        "^|^1^|^01:27:42^|^24418435.batch^|^24418435.batch^|^499092K^|^1^|^1Gn^|^"
-        "COMPLETED^|^01:27:29\n"
-        "^|^1^|^01:27:42^|^24418435.extern^|^24418435.extern^|^1376K^|^1^|^1Gn^|^"
-        "COMPLETED^|^00:00:00\n"
-        "^|^1^|^21:14:48^|^25569410^|^25569410^|^^|^1^|^4000Mc^|^COMPLETED^|^19:28:36\n"
-        "^|^1^|^21:14:49^|^25569410.extern^|^25569410.extern^|^1548K^|^1^|^4000Mc^|^"
-        "COMPLETED^|^00:00:00\n"
-        "^|^1^|^21:14:43^|^25569410.0^|^25569410.0^|^62328K"
-        "^|^1^|^4000Mc^|^COMPLETED^|^19:28:36\n"
+        console_jobs["24418435_notime"] + console_jobs["25569410_notime"]
     )
     mocker.patch("reportseff.db_inquirer.subprocess.run", return_value=sub_result)
     result = runner.invoke(
@@ -257,24 +211,14 @@ def test_simple_user(mocker, mock_inquirer):
     assert output[1].split() == ["25569410", "COMPLETED", "21:14:48", "91.7%", "1.5%"]
 
 
-def test_simple_partition(mocker, mock_inquirer):
+def test_simple_partition(mocker, mock_inquirer, console_jobs):
     """Can limit outputs by partition."""
     mocker.patch("reportseff.console.which", return_value=True)
     runner = CliRunner()
     sub_result = mocker.MagicMock()
     sub_result.returncode = 0
     sub_result.stdout = (
-        "^|^1^|^01:27:42^|^24418435^|^24418435^|^^|^1^|^1Gn^|^"
-        "COMPLETED^|^01:27:29\n"
-        "^|^1^|^01:27:42^|^24418435.batch^|^24418435.batch^|^499092K^|^1^|^1Gn^|^"
-        "COMPLETED^|^01:27:29\n"
-        "^|^1^|^01:27:42^|^24418435.extern^|^24418435.extern^|^1376K^|^1^|^1Gn^|^"
-        "COMPLETED^|^00:00:00\n"
-        "^|^1^|^21:14:48^|^25569410^|^25569410^|^^|^1^|^4000Mc^|^COMPLETED^|^19:28:36\n"
-        "^|^1^|^21:14:49^|^25569410.extern^|^25569410.extern^|^1548K^|^1^|^4000Mc^|^"
-        "COMPLETED^|^00:00:00\n"
-        "^|^1^|^21:14:43^|^25569410.0^|^25569410.0"
-        "^|^62328K^|^1^|^4000Mc^|^COMPLETED^|^19:28:36\n"
+        console_jobs["24418435_notime"] + console_jobs["25569410_notime"]
     )
     mocker.patch("reportseff.db_inquirer.subprocess.run", return_value=sub_result)
     result = runner.invoke(
@@ -310,24 +254,14 @@ def test_format_add(mocker, mock_inquirer):
     )
 
 
-def test_since(mocker, mock_inquirer):
+def test_since(mocker, mock_inquirer, console_jobs):
     """Can limit outputs by time since argument."""
     mocker.patch("reportseff.console.which", return_value=True)
     runner = CliRunner()
     sub_result = mocker.MagicMock()
     sub_result.returncode = 0
     sub_result.stdout = (
-        "^|^1^|^01:27:42^|^24418435^|^24418435^|^^|^1^|^1Gn^|^"
-        "COMPLETED^|^01:27:29\n"
-        "^|^1^|^01:27:42^|^24418435.batch^|^24418435.batch^|^499092K^|^1^|^1Gn^|^"
-        "COMPLETED^|^01:27:29\n"
-        "^|^1^|^01:27:42^|^24418435.extern^|^24418435.extern^|^1376K^|^1^|^1Gn^|^"
-        "COMPLETED^|^00:00:00\n"
-        "^|^1^|^21:14:48^|^25569410^|^25569410^|^^|^1^|^4000Mc^|^COMPLETED^|^19:28:36\n"
-        "^|^1^|^21:14:49^|^25569410.extern^|^25569410.extern^|^1548K^|^1^|^4000Mc^|^"
-        "COMPLETED^|^00:00:00\n"
-        "^|^1^|^21:14:43^|^25569410.0^|^25569410.0^|^62328K"
-        "^|^1^|^4000Mc^|^COMPLETED^|^19:28:36\n"
+        console_jobs["24418435_notime"] + console_jobs["25569410_notime"]
     )
     mocker.patch("reportseff.db_inquirer.subprocess.run", return_value=sub_result)
     result = runner.invoke(
@@ -345,24 +279,14 @@ def test_since(mocker, mock_inquirer):
     assert output[1].split() == ["25569410", "COMPLETED", "21:14:48", "91.7%", "1.5%"]
 
 
-def test_since_all_users(mocker, mock_inquirer):
+def test_since_all_users(mocker, mock_inquirer, console_jobs):
     """Can limit outputs by time since argument."""
     mocker.patch("reportseff.console.which", return_value=True)
     runner = CliRunner()
     sub_result = mocker.MagicMock()
     sub_result.returncode = 0
     sub_result.stdout = (
-        "^|^1^|^01:27:42^|^24418435^|^24418435^|^^|^1^|^1Gn^|^"
-        "COMPLETED^|^01:27:29\n"
-        "^|^1^|^01:27:42^|^24418435.batch^|^24418435.batch^|^499092K^|^1^|^1Gn^|^"
-        "COMPLETED^|^01:27:29\n"
-        "^|^1^|^01:27:42^|^24418435.extern^|^24418435.extern^|^1376K^|^1^|^1Gn^|^"
-        "COMPLETED^|^00:00:00\n"
-        "^|^1^|^21:14:48^|^25569410^|^25569410^|^^|^1^|^4000Mc^|^COMPLETED^|^19:28:36\n"
-        "^|^1^|^21:14:49^|^25569410.extern^|^25569410.extern^|^1548K^|^1^|^4000Mc^|^"
-        "COMPLETED^|^00:00:00\n"
-        "^|^1^|^21:14:43^|^25569410.0^|^25569410.0^|^62328K"
-        "^|^1^|^4000Mc^|^COMPLETED^|^19:28:36\n"
+        console_jobs["24418435_notime"] + console_jobs["25569410_notime"]
     )
     mock_sub = mocker.patch(
         "reportseff.db_inquirer.subprocess.run", return_value=sub_result
@@ -385,7 +309,7 @@ def test_since_all_users(mocker, mock_inquirer):
         args=(
             "sacct -P -n --delimiter=^|^ "
             "--format=AdminComment,AllocCPUS,Elapsed,JobID,JobIDRaw,"
-            "MaxRSS,NNodes,REQMEM,State,TotalCPU "
+            "MaxRSS,NNodes,NTasks,REQMEM,State,TotalCPU "
             "--allusers "  # all users is added since no jobs/files were specified
             "--starttime=200406"
         ).split(),
@@ -397,24 +321,14 @@ def test_since_all_users(mocker, mock_inquirer):
     )
 
 
-def test_since_all_users_partition(mocker, mock_inquirer):
+def test_since_all_users_partition(mocker, mock_inquirer, console_jobs):
     """Can limit outputs by time since and partition argument."""
     mocker.patch("reportseff.console.which", return_value=True)
     runner = CliRunner()
     sub_result = mocker.MagicMock()
     sub_result.returncode = 0
     sub_result.stdout = (
-        "^|^1^|^01:27:42^|^24418435^|^24418435^|^^|^1^|^1Gn^|^"
-        "COMPLETED^|^01:27:29\n"
-        "^|^1^|^01:27:42^|^24418435.batch^|^24418435.batch^|^499092K^|^1^|^1Gn^|^"
-        "COMPLETED^|^01:27:29\n"
-        "^|^1^|^01:27:42^|^24418435.extern^|^24418435.extern^|^1376K^|^1^|^1Gn^|^"
-        "COMPLETED^|^00:00:00\n"
-        "^|^1^|^21:14:48^|^25569410^|^25569410^|^^|^1^|^4000Mc^|^COMPLETED^|^19:28:36\n"
-        "^|^1^|^21:14:49^|^25569410.extern^|^25569410.extern^|^1548K^|^1^|^4000Mc^|^"
-        "COMPLETED^|^00:00:00\n"
-        "^|^1^|^21:14:43^|^25569410.0^|^25569410.0^|^62328K"
-        "^|^1^|^4000Mc^|^COMPLETED^|^19:28:36\n"
+        console_jobs["24418435_notime"] + console_jobs["25569410_notime"]
     )
     mock_sub = mocker.patch(
         "reportseff.db_inquirer.subprocess.run", return_value=sub_result
@@ -437,7 +351,7 @@ def test_since_all_users_partition(mocker, mock_inquirer):
         args=(
             "sacct -P -n --delimiter=^|^ "
             "--format=AdminComment,AllocCPUS,Elapsed,JobID,JobIDRaw,"
-            "MaxRSS,NNodes,REQMEM,State,TotalCPU "
+            "MaxRSS,NNodes,NTasks,REQMEM,State,TotalCPU "
             "--allusers "  # all users is added since no jobs/files were specified
             "--starttime=200406 "
             "--partition=partition "
@@ -450,25 +364,15 @@ def test_since_all_users_partition(mocker, mock_inquirer):
     )
 
 
-def test_parsable(mocker, mock_inquirer):
+def test_parsable(mocker, mock_inquirer, console_jobs):
     """Can display output as parsable format."""
     mocker.patch("reportseff.console.which", return_value=True)
     runner = CliRunner()
     sub_result = mocker.MagicMock()
     sub_result.returncode = 0
-    sub_result.stdout = (
-        "^|^1^|^01:27:42^|^24418435^|^24418435^|^^|^1^|^1Gn^|^"
-        "COMPLETED^|^01:27:29\n"
-        "^|^1^|^01:27:42^|^24418435.batch^|^24418435.batch^|^499092K^|^1^|^1Gn^|^"
-        "COMPLETED^|^01:27:29\n"
-        "^|^1^|^01:27:42^|^24418435.extern^|^24418435.extern^|^1376K^|^1^|^1Gn^|^"
-        "COMPLETED^|^00:00:00\n"
-        "^|^1^|^21:14:48^|^25569410^|^25569410^|^^|^1^|^4000Mc^|^RUNNING^|^19:28:36\n"
-        "^|^1^|^21:14:49^|^25569410.extern^|^25569410.extern^|^1548K^|^1^|^4000Mc^|^"
-        "RUNNING^|^00:00:00\n"
-        "^|^1^|^21:14:43^|^25569410.0^|^25569410.0^|^62328K"
-        "^|^1^|^4000Mc^|^RUNNING^|^19:28:36\n"
-    )
+    sub_result.stdout = console_jobs["24418435_notime"] + console_jobs[
+        "25569410_notime"
+    ].replace("COMPLETED", "RUNNING")
     mocker.patch("reportseff.db_inquirer.subprocess.run", return_value=sub_result)
     result = runner.invoke(
         console.main,
@@ -483,29 +387,18 @@ def test_parsable(mocker, mock_inquirer):
     output = result.output.split("\n")[1:]
     # no color/bold codes and ^|^ delimited
     assert output[0].split("|") == ["24418435", "COMPLETED", "01:27:42", "99.8", "47.6"]
-    # other is suppressed by state filter
     assert output[1].split("|") == ["25569410", "RUNNING", "21:14:48", "---", "---"]
 
 
-def test_simple_state(mocker, mock_inquirer):
+def test_simple_state(mocker, mock_inquirer, console_jobs):
     """Can limit outputs by filtering state."""
     mocker.patch("reportseff.console.which", return_value=True)
     runner = CliRunner()
     sub_result = mocker.MagicMock()
     sub_result.returncode = 0
-    sub_result.stdout = (
-        "^|^1^|^01:27:42^|^24418435^|^24418435^|^^|^1^|^1Gn^|^"
-        "COMPLETED^|^01:27:29\n"
-        "^|^1^|^01:27:42^|^24418435.batch^|^24418435.batch^|^499092K^|^1^|^1Gn^|^"
-        "COMPLETED^|^01:27:29\n"
-        "^|^1^|^01:27:42^|^24418435.extern^|^24418435.extern^|^1376K^|^1^|^1Gn^|^"
-        "COMPLETED^|^00:00:00\n"
-        "^|^1^|^21:14:48^|^25569410^|^25569410^|^^|^1^|^4000Mc^|^RUNNING^|^19:28:36\n"
-        "^|^1^|^21:14:49^|^25569410.extern^|^25569410.extern^|^1548K^|^1^|^4000Mc^|^"
-        "RUNNING^|^00:00:00\n"
-        "^|^1^|^21:14:43^|^25569410.0^|^25569410.0^|^62328K"
-        "^|^1^|^4000Mc^|^RUNNING^|^19:28:36\n"
-    )
+    sub_result.stdout = console_jobs["24418435_notime"] + console_jobs[
+        "25569410_notime"
+    ].replace("COMPLETED", "RUNNING")
     mocker.patch("reportseff.db_inquirer.subprocess.run", return_value=sub_result)
     result = runner.invoke(
         console.main,
@@ -523,25 +416,15 @@ def test_simple_state(mocker, mock_inquirer):
     assert output[1].split() == []
 
 
-def test_simple_not_state(mocker, mock_inquirer):
+def test_simple_not_state(mocker, mock_inquirer, console_jobs):
     """Can limit outputs by removing state."""
     mocker.patch("reportseff.console.which", return_value=True)
     runner = CliRunner()
     sub_result = mocker.MagicMock()
     sub_result.returncode = 0
-    sub_result.stdout = (
-        "^|^1^|^01:27:42^|^24418435^|^24418435^|^^|^1^|^1Gn^|^"
-        "COMPLETED^|^01:27:29\n"
-        "^|^1^|^01:27:42^|^24418435.batch^|^24418435.batch^|^499092K^|^1^|^1Gn^|^"
-        "COMPLETED^|^01:27:29\n"
-        "^|^1^|^01:27:42^|^24418435.extern^|^24418435.extern^|^1376K^|^1^|^1Gn^|^"
-        "COMPLETED^|^00:00:00\n"
-        "^|^1^|^21:14:48^|^25569410^|^25569410^|^^|^1^|^4000Mc^|^RUNNING^|^19:28:36\n"
-        "^|^1^|^21:14:49^|^25569410.extern^|^25569410.extern^|^1548K^|^1^|^4000Mc^|^"
-        "RUNNING^|^00:00:00\n"
-        "^|^1^|^21:14:43^|^25569410.0^|^25569410.0^|^62328K"
-        "^|^1^|^4000Mc^|^RUNNING^|^19:28:36\n"
-    )
+    sub_result.stdout = console_jobs["24418435_notime"] + console_jobs[
+        "25569410_notime"
+    ].replace("COMPLETED", "RUNNING")
     mocker.patch("reportseff.db_inquirer.subprocess.run", return_value=sub_result)
     result = runner.invoke(
         console.main,
@@ -559,25 +442,15 @@ def test_simple_not_state(mocker, mock_inquirer):
     assert output[1].split() == []
 
 
-def test_invalid_not_state(mocker, mock_inquirer):
+def test_invalid_not_state(mocker, mock_inquirer, console_jobs):
     """When not state isn't found, return all jobs."""
     mocker.patch("reportseff.console.which", return_value=True)
     runner = CliRunner()
     sub_result = mocker.MagicMock()
     sub_result.returncode = 0
-    sub_result.stdout = (
-        "^|^1^|^01:27:42^|^24418435^|^24418435^|^^|^1^|^1Gn^|^"
-        "COMPLETED^|^01:27:29\n"
-        "^|^1^|^01:27:42^|^24418435.batch^|^24418435.batch^|^499092K^|^1^|^1Gn^|^"
-        "COMPLETED^|^01:27:29\n"
-        "^|^1^|^01:27:42^|^24418435.extern^|^24418435.extern^|^1376K^|^1^|^1Gn^|^"
-        "COMPLETED^|^00:00:00\n"
-        "^|^1^|^21:14:48^|^25569410^|^25569410^|^^|^1^|^4000Mc^|^RUNNING^|^19:28:36\n"
-        "^|^1^|^21:14:49^|^25569410.extern^|^25569410.extern^|^1548K^|^1^|^4000Mc^|^"
-        "RUNNING^|^00:00:00\n"
-        "^|^1^|^21:14:43^|^25569410.0^|^25569410.0^|^62328K"
-        "^|^1^|^4000Mc^|^RUNNING^|^19:28:36\n"
-    )
+    sub_result.stdout = console_jobs["24418435_notime"] + console_jobs[
+        "25569410_notime"
+    ].replace("COMPLETED", "RUNNING")
     mocker.patch("reportseff.db_inquirer.subprocess.run", return_value=sub_result)
     result = runner.invoke(
         console.main,
@@ -598,25 +471,15 @@ def test_invalid_not_state(mocker, mock_inquirer):
     assert output[5].split() == []
 
 
-def test_no_state(mocker, mock_inquirer):
+def test_no_state(mocker, mock_inquirer, console_jobs):
     """Unknown states produce empty output."""
     mocker.patch("reportseff.console.which", return_value=True)
     runner = CliRunner()
     sub_result = mocker.MagicMock()
     sub_result.returncode = 0
-    sub_result.stdout = (
-        "^|^1^|^01:27:42^|^24418435^|^24418435^|^^|^1^|^1Gn^|^"
-        "COMPLETED^|^01:27:29\n"
-        "^|^1^|^01:27:42^|^24418435.batch^|^24418435.batch^|^499092K^|^1^|^1Gn^|^"
-        "COMPLETED^|^01:27:29\n"
-        "^|^1^|^01:27:42^|^24418435.extern^|^24418435.extern^|^1376K^|^1^|^1Gn^|^"
-        "COMPLETED^|^00:00:00\n"
-        "^|^1^|^21:14:48^|^25569410^|^25569410^|^^|^1^|^4000Mc^|^RUNNING^|^19:28:36\n"
-        "^|^1^|^21:14:49^|^25569410.extern^|^25569410.extern"
-        "^|^1548K^|^1^|^4000Mc^|^RUNNING^|^00:00:00\n"
-        "^|^1^|^21:14:43^|^25569410.0^|^25569410.0^|^62328K"
-        "^|^1^|^4000Mc^|^RUNNING^|^19:28:36\n"
-    )
+    sub_result.stdout = console_jobs["24418435_notime"] + console_jobs[
+        "25569410_notime"
+    ].replace("COMPLETED", "RUNNING")
     mocker.patch("reportseff.db_inquirer.subprocess.run", return_value=sub_result)
     result = runner.invoke(
         console.main, "--no-color --state ZZ 25569410 24418435".split()
@@ -638,20 +501,13 @@ def test_no_state(mocker, mock_inquirer):
     assert output[3] == ""
 
 
-def test_array_job_raw_id(mocker, mock_inquirer):
+def test_array_job_raw_id(mocker, mock_inquirer, console_jobs):
     """Can find job array by base id."""
     mocker.patch("reportseff.console.which", return_value=True)
     runner = CliRunner()
     sub_result = mocker.MagicMock()
     sub_result.returncode = 0
-    sub_result.stdout = (
-        "^|^1^|^00:09:34^|^24220929_421^|^24221219^|^^|^1^|^16000Mn^|^"
-        "COMPLETED^|^09:28.052\n"
-        "^|^1^|^00:09:34^|^24220929_421.batch^|^24221219.batch"
-        "^|^5664932K^|^1^|^16000Mn^|^COMPLETED^|^09:28.051\n"
-        "^|^1^|^00:09:34^|^24220929_421.extern^|^24221219.extern"
-        "^|^1404K^|^1^|^16000Mn^|^COMPLETED^|^00:00:00\n"
-    )
+    sub_result.stdout = console_jobs["24221219"]
     mocker.patch("reportseff.db_inquirer.subprocess.run", return_value=sub_result)
     result = runner.invoke(
         console.main,
@@ -671,26 +527,13 @@ def test_array_job_raw_id(mocker, mock_inquirer):
     assert len(output) == 1
 
 
-def test_array_job_single(mocker, mock_inquirer):
+def test_array_job_single(mocker, mock_inquirer, console_jobs):
     """Can get single array job element."""
     mocker.patch("reportseff.console.which", return_value=True)
     runner = CliRunner()
     sub_result = mocker.MagicMock()
     sub_result.returncode = 0
-    sub_result.stdout = (
-        "^|^1^|^00:09:34^|^24220929_421^|^24221219^|^^|^1^|^16000Mn^|^"
-        "COMPLETED^|^09:28.052\n"
-        "^|^1^|^00:09:34^|^24220929_421.batch^|^24221219.batch"
-        "^|^5664932K^|^1^|^16000Mn^|^COMPLETED^|^09:28.051\n"
-        "^|^1^|^00:09:34^|^24220929_421.extern^|^24221219.extern"
-        "^|^1404K^|^1^|^16000Mn^|^COMPLETED^|^00:00:00\n"
-        "^|^1^|^00:09:33^|^24220929_431^|^24221220^|^^|^1^|^16000Mn^|^"
-        "PENDING^|^09:27.460\n"
-        "^|^1^|^00:09:33^|^24220929_431.batch^|^24221220.batch"
-        "^|^5518572K^|^1^|^16000Mn^|^PENDING^|^09:27.459\n"
-        "^|^1^|^00:09:33^|^24220929_431.extern^|^24221220.extern"
-        "^|^1400K^|^1^|^16000Mn^|^PENDING^|^00:00:00\n"
-    )
+    sub_result.stdout = console_jobs["24221219"] + console_jobs["24221220"]
     mocker.patch("reportseff.db_inquirer.subprocess.run", return_value=sub_result)
     result = runner.invoke(
         console.main,
@@ -712,26 +555,13 @@ def test_array_job_single(mocker, mock_inquirer):
     assert len(output) == 1
 
 
-def test_array_job_base(mocker, mock_inquirer):
+def test_array_job_base(mocker, mock_inquirer, console_jobs):
     """Base array job id gets all elements."""
     mocker.patch("reportseff.console.which", return_value=True)
     runner = CliRunner()
     sub_result = mocker.MagicMock()
     sub_result.returncode = 0
-    sub_result.stdout = (
-        "^|^1^|^00:09:34^|^24220929_421^|^24221219^|^^|^1^|^16000Mn^|^"
-        "COMPLETED^|^09:28.052\n"
-        "^|^1^|^00:09:34^|^24220929_421.batch^|^24221219.batch^|^"
-        "5664932K^|^1^|^16000Mn^|^COMPLETED^|^09:28.051\n"
-        "^|^1^|^00:09:34^|^24220929_421.extern^|^24221219.extern^|^"
-        "1404K^|^1^|^16000Mn^|^COMPLETED^|^00:00:00\n"
-        "^|^1^|^00:09:33^|^24220929_431^|^24221220^|^^|^1^|^16000Mn^|^"
-        "PENDING^|^09:27.460\n"
-        "^|^1^|^00:09:33^|^24220929_431.batch^|^24221220.batch^|^"
-        "5518572K^|^1^|^16000Mn^|^PENDING^|^09:27.459\n"
-        "^|^1^|^00:09:33^|^24220929_431.extern^|^24221220.extern^|^"
-        "1400K^|^1^|^16000Mn^|^PENDING^|^00:00:00\n"
-    )
+    sub_result.stdout = console_jobs["24221219"] + console_jobs["24221220"]
     mocker.patch("reportseff.db_inquirer.subprocess.run", return_value=sub_result)
     result = runner.invoke(
         console.main,
@@ -789,19 +619,13 @@ def test_empty_sacct(mocker, mock_inquirer):
     assert len(output) == 1
 
 
-def test_failed_no_mem(mocker, mock_inquirer):
+def test_failed_no_mem(mocker, mock_inquirer, console_jobs):
     """Empty memory entries produce valid output."""
     mocker.patch("reportseff.console.which", return_value=True)
     runner = CliRunner()
     sub_result = mocker.MagicMock()
     sub_result.returncode = 0
-    sub_result.stdout = (
-        "^|^8^|^00:00:12^|^23000381^|^23000381^|^^|^1^|^4000Mc^|^FAILED^|^00:00:00\n"
-        "^|^8^|^00:00:12^|^23000381.batch^|^23000381.batch^|^^|^1^|^4000Mc^|^"
-        "FAILED^|^00:00:00\n"
-        "^|^8^|^00:00:12^|^23000381.extern^|^23000381.extern^|^1592K^|^1^|^4000Mc^|^"
-        "COMPLETED^|^00:00:00\n"
-    )
+    sub_result.stdout = console_jobs["23000381"]
     mocker.patch("reportseff.db_inquirer.subprocess.run", return_value=sub_result)
     result = runner.invoke(console.main, "--no-color 23000381".split())
 
@@ -812,16 +636,13 @@ def test_failed_no_mem(mocker, mock_inquirer):
     assert len(output) == 1
 
 
-def test_canceled_by_other(mocker, mock_inquirer):
+def test_canceled_by_other(mocker, mock_inquirer, console_jobs):
     """Canceled states are correctly handled."""
     mocker.patch("reportseff.console.which", return_value=True)
     runner = CliRunner()
     sub_result = mocker.MagicMock()
     sub_result.returncode = 0
-    sub_result.stdout = (
-        "^|^16^|^00:00:00^|^23000233^|^23000233^|^^|^1^|^"
-        "4000Mc^|^CANCELLED by 129319^|^00:00:00\n"
-    )
+    sub_result.stdout = console_jobs["23000233"]
     mocker.patch("reportseff.db_inquirer.subprocess.run", return_value=sub_result)
     result = runner.invoke(console.main, "--no-color 23000233 --state CA".split())
 
@@ -832,27 +653,20 @@ def test_canceled_by_other(mocker, mock_inquirer):
         "23000233",
         "CANCELLED",
         "00:00:00",
-        "---",
+        "0.0%",
         "---",
         "0.0%",
     ]
     assert len(output) == 1
 
 
-def test_zero_runtime(mocker, mock_inquirer):
+def test_zero_runtime(mocker, mock_inquirer, console_jobs):
     """Entries with zero runtime produce reasonable timeeff."""
     mocker.patch("reportseff.console.which", return_value=True)
     runner = CliRunner()
     sub_result = mocker.MagicMock()
     sub_result.returncode = 0
-    sub_result.stdout = (
-        "^|^8^|^00:00:00^|^23000210^|^23000210^|^^|^1^|^20000Mn^|^"
-        "FAILED^|^00:00.007\n"
-        "^|^8^|^00:00:00^|^23000210.batch^|^23000210.batch^|^1988K^|^1^|^20000Mn^|^"
-        "FAILED^|^00:00.006\n"
-        "^|^8^|^00:00:00^|^23000210.extern^|^23000210.extern^|^1556K^|^1^|^20000Mn^|^"
-        "COMPLETED^|^00:00:00\n"
-    )
+    sub_result.stdout = console_jobs["23000210"]
     mocker.patch("reportseff.db_inquirer.subprocess.run", return_value=sub_result)
     result = runner.invoke(console.main, "--no-color 23000210".split())
 
@@ -875,56 +689,64 @@ def test_no_systems(mocker, mock_inquirer):
     assert output[0] == "No supported scheduling systems found!"
 
 
-def test_issue_16(mocker, mock_inquirer):
+def test_issue_16(mocker, mock_inquirer, console_jobs):
     """Incorrect memory usage for multi-node jobs."""
     mocker.patch("reportseff.console.which", return_value=True)
     runner = CliRunner()
     sub_result = mocker.MagicMock()
     sub_result.returncode = 0
     sub_result.stdout = """
-^|^16^|^07:36:03^|^65638294^|^65638294^|^^|^2^|^32G\
+^|^16^|^07:36:03^|^65638294^|^65638294^|^^|^1^|^2^|^32G\
 ^|^COMPLETED^|^6-23:59:00^|^4-23:56:21
 ^|^1^|^07:36:03^|^65638294.batch^|^65638294.batch\
-^|^1147220K^|^1^|^^|^COMPLETED^|^^|^07:30:20
+^|^1147220K^|^1^|^1^|^^|^COMPLETED^|^^|^07:30:20
 ^|^16^|^07:36:03^|^65638294.extern^|^65638294.extern\
-^|^0^|^2^|^^|^COMPLETED^|^^|^00:00.001
-^|^15^|^00:00:11^|^65638294.0^|^65638294.0^|^0^|^1^|^^|^COMPLETED^|^^|^00:11.830
-^|^15^|^00:02:15^|^65638294.1^|^65638294.1^|^4455540K^|^1^|^^|^COMPLETED^|^^|^31:09.458
-^|^15^|^00:00:10^|^65638294.2^|^65638294.2^|^0^|^1^|^^|^COMPLETED^|^^|^00:00:04
-^|^15^|^00:00:08^|^65638294.3^|^65638294.3^|^0^|^1^|^^|^COMPLETED^|^^|^00:09.602
-^|^15^|^00:00:07^|^65638294.4^|^65638294.4^|^0^|^1^|^^|^COMPLETED^|^^|^00:56.827
-^|^15^|^00:00:06^|^65638294.5^|^65638294.5^|^0^|^1^|^^|^COMPLETED^|^^|^00:03.512
-^|^15^|^00:00:08^|^65638294.6^|^65638294.6^|^0^|^1^|^^|^COMPLETED^|^^|^00:08.520
-^|^15^|^00:00:13^|^65638294.7^|^65638294.7^|^0^|^1^|^^|^COMPLETED^|^^|^01:02.013
-^|^15^|^00:00:02^|^65638294.8^|^65638294.8^|^0^|^1^|^^|^COMPLETED^|^^|^00:03.639
-^|^15^|^00:00:06^|^65638294.9^|^65638294.9^|^0^|^1^|^^|^COMPLETED^|^^|^00:08.683
-^|^15^|^00:00:08^|^65638294.10^|^65638294.10^|^0^|^1^|^^|^COMPLETED^|^^|^00:57.438
-^|^15^|^00:00:06^|^65638294.11^|^65638294.11^|^0^|^1^|^^|^COMPLETED^|^^|^00:03.642
-^|^15^|^00:00:09^|^65638294.12^|^65638294.12^|^0^|^1^|^^|^COMPLETED^|^^|^00:10.271
+^|^0^|^1^|^2^|^^|^COMPLETED^|^^|^00:00.001
+^|^15^|^00:00:11^|^65638294.0^|^65638294.0^|^0^|^1^|^1^|^^|^COMPLETED^|^^|^00:11.830
+^|^15^|^00:02:15^|^65638294.1^|^65638294.1^|^4455540K\
+^|^1^|^1^|^^|^COMPLETED^|^^|^31:09.458
+^|^15^|^00:00:10^|^65638294.2^|^65638294.2^|^0^|^1^|^1^|^^|^COMPLETED^|^^|^00:00:04
+^|^15^|^00:00:08^|^65638294.3^|^65638294.3^|^0^|^1^|^1^|^^|^COMPLETED^|^^|^00:09.602
+^|^15^|^00:00:07^|^65638294.4^|^65638294.4^|^0^|^1^|^1^|^^|^COMPLETED^|^^|^00:56.827
+^|^15^|^00:00:06^|^65638294.5^|^65638294.5^|^0^|^1^|^1^|^^|^COMPLETED^|^^|^00:03.512
+^|^15^|^00:00:08^|^65638294.6^|^65638294.6^|^0^|^1^|^1^|^^|^COMPLETED^|^^|^00:08.520
+^|^15^|^00:00:13^|^65638294.7^|^65638294.7^|^0^|^1^|^1^|^^|^COMPLETED^|^^|^01:02.013
+^|^15^|^00:00:02^|^65638294.8^|^65638294.8^|^0^|^1^|^1^|^^|^COMPLETED^|^^|^00:03.639
+^|^15^|^00:00:06^|^65638294.9^|^65638294.9^|^0^|^1^|^1^|^^|^COMPLETED^|^^|^00:08.683
+^|^15^|^00:00:08^|^65638294.10^|^65638294.10^|^0^|^1^|^1^|^^|^COMPLETED^|^^|^00:57.438
+^|^15^|^00:00:06^|^65638294.11^|^65638294.11^|^0^|^1^|^1^|^^|^COMPLETED^|^^|^00:03.642
+^|^15^|^00:00:09^|^65638294.12^|^65638294.12^|^0^|^1^|^1^|^^|^COMPLETED^|^^|^00:10.271
 ^|^15^|^00:01:24^|^65638294.13^|^65638294.13^|^4149700K\
-^|^1^|^^|^COMPLETED^|^^|^17:18.067
-^|^15^|^00:00:01^|^65638294.14^|^65638294.14^|^0^|^1^|^^|^COMPLETED^|^^|^00:03.302
-^|^15^|^00:00:10^|^65638294.15^|^65638294.15^|^0^|^1^|^^|^COMPLETED^|^^|^00:14.615
-^|^15^|^00:06:45^|^65638294.16^|^65638294.16^|^4748052K^|^1^|^^|^COMPLETED^|^^|^01:36:40
-^|^15^|^00:00:10^|^65638294.17^|^65638294.17^|^0^|^1^|^^|^COMPLETED^|^^|^00:03.864
-^|^15^|^00:00:09^|^65638294.18^|^65638294.18^|^0^|^1^|^^|^COMPLETED^|^^|^00:48.987
-^|^15^|^01:32:53^|^65638294.19^|^65638294.19^|^7734356K^|^1^|^^|^COMPLETED^|^^|^23:09:33
-^|^15^|^00:00:01^|^65638294.20^|^65638294.20^|^0^|^1^|^^|^COMPLETED^|^^|^00:03.520
-^|^15^|^00:00:07^|^65638294.21^|^65638294.21^|^0^|^1^|^^|^COMPLETED^|^^|^00:50.015
-^|^15^|^00:55:17^|^65638294.22^|^65638294.22^|^8074500K^|^1^|^^|^COMPLETED^|^^|^13:45:29
-^|^15^|^00:00:13^|^65638294.23^|^65638294.23^|^0^|^1^|^^|^COMPLETED^|^^|^00:04.413
-^|^15^|^00:00:12^|^65638294.24^|^65638294.24^|^0^|^1^|^^|^COMPLETED^|^^|^00:49.100
-^|^15^|^00:57:41^|^65638294.25^|^65638294.25^|^7883152K^|^1^|^^|^COMPLETED^|^^|^14:20:36
-^|^15^|^00:00:01^|^65638294.26^|^65638294.26^|^0^|^1^|^^|^COMPLETED^|^^|^00:03.953
-^|^15^|^00:00:05^|^65638294.27^|^65638294.27^|^0^|^1^|^^|^COMPLETED^|^^|^00:47.223
-^|^15^|^01:00:17^|^65638294.28^|^65638294.28^|^7715752K^|^1^|^^|^COMPLETED^|^^|^14:59:40
-^|^15^|^00:00:06^|^65638294.29^|^65638294.29^|^0^|^1^|^^|^COMPLETED^|^^|^00:04.341
-^|^15^|^00:00:07^|^65638294.30^|^65638294.30^|^0^|^1^|^^|^COMPLETED^|^^|^00:50.416
-^|^15^|^01:22:31^|^65638294.31^|^65638294.31^|^7663264K^|^1^|^^|^COMPLETED^|^^|^20:33:59
-^|^15^|^00:00:05^|^65638294.32^|^65638294.32^|^0^|^1^|^^|^COMPLETED^|^^|^00:04.199
-^|^15^|^00:00:08^|^65638294.33^|^65638294.33^|^0^|^1^|^^|^COMPLETED^|^^|^00:50.009
-^|^15^|^01:32:23^|^65638294.34^|^65638294.34^|^7764884K^|^1^|^^|^COMPLETED^|^^|^23:01:52
-^|^15^|^00:00:06^|^65638294.35^|^65638294.35^|^0^|^1^|^^|^COMPLETED^|^^|^00:04.527
+^|^1^|^1^|^^|^COMPLETED^|^^|^17:18.067
+^|^15^|^00:00:01^|^65638294.14^|^65638294.14^|^0^|^1^|^1^|^^|^COMPLETED^|^^|^00:03.302
+^|^15^|^00:00:10^|^65638294.15^|^65638294.15^|^0^|^1^|^1^|^^|^COMPLETED^|^^|^00:14.615
+^|^15^|^00:06:45^|^65638294.16^|^65638294.16^|^4748052K\
+^|^1^|^1^|^^|^COMPLETED^|^^|^01:36:40
+^|^15^|^00:00:10^|^65638294.17^|^65638294.17^|^0^|^1^|^1^|^^|^COMPLETED^|^^|^00:03.864
+^|^15^|^00:00:09^|^65638294.18^|^65638294.18^|^0^|^1^|^1^|^^|^COMPLETED^|^^|^00:48.987
+^|^15^|^01:32:53^|^65638294.19^|^65638294.19^|^7734356K\
+^|^1^|^1^|^^|^COMPLETED^|^^|^23:09:33
+^|^15^|^00:00:01^|^65638294.20^|^65638294.20^|^0^|^1^|^1^|^^|^COMPLETED^|^^|^00:03.520
+^|^15^|^00:00:07^|^65638294.21^|^65638294.21^|^0^|^1^|^1^|^^|^COMPLETED^|^^|^00:50.015
+^|^15^|^00:55:17^|^65638294.22^|^65638294.22^|^8074500K\
+^|^1^|^1^|^^|^COMPLETED^|^^|^13:45:29
+^|^15^|^00:00:13^|^65638294.23^|^65638294.23^|^0^|^1^|^1^|^^|^COMPLETED^|^^|^00:04.413
+^|^15^|^00:00:12^|^65638294.24^|^65638294.24^|^0^|^1^|^1^|^^|^COMPLETED^|^^|^00:49.100
+^|^15^|^00:57:41^|^65638294.25^|^65638294.25^|^7883152K\
+^|^1^|^1^|^^|^COMPLETED^|^^|^14:20:36
+^|^15^|^00:00:01^|^65638294.26^|^65638294.26^|^0^|^1^|^1^|^^|^COMPLETED^|^^|^00:03.953
+^|^15^|^00:00:05^|^65638294.27^|^65638294.27^|^0^|^1^|^1^|^^|^COMPLETED^|^^|^00:47.223
+^|^15^|^01:00:17^|^65638294.28^|^65638294.28^|^7715752K\
+^|^1^|^1^|^^|^COMPLETED^|^^|^14:59:40
+^|^15^|^00:00:06^|^65638294.29^|^65638294.29^|^0^|^1^|^1^|^^|^COMPLETED^|^^|^00:04.341
+^|^15^|^00:00:07^|^65638294.30^|^65638294.30^|^0^|^1^|^1^|^^|^COMPLETED^|^^|^00:50.416
+^|^15^|^01:22:31^|^65638294.31^|^65638294.31^|^7663264K\
+^|^1^|^1^|^^|^COMPLETED^|^^|^20:33:59
+^|^15^|^00:00:05^|^65638294.32^|^65638294.32^|^0^|^1^|^1^|^^|^COMPLETED^|^^|^00:04.199
+^|^15^|^00:00:08^|^65638294.33^|^65638294.33^|^0^|^1^|^1^|^^|^COMPLETED^|^^|^00:50.009
+^|^15^|^01:32:23^|^65638294.34^|^65638294.34^|^7764884K\
+^|^1^|^1^|^^|^COMPLETED^|^^|^23:01:52
+^|^15^|^00:00:06^|^65638294.35^|^65638294.35^|^0^|^1^|^1^|^^|^COMPLETED^|^^|^00:04.527
 """
     mocker.patch("reportseff.db_inquirer.subprocess.run", return_value=sub_result)
     result = runner.invoke(console.main, "--no-color 65638294".split())
@@ -943,36 +765,36 @@ def test_issue_16(mocker, mock_inquirer):
     assert len(output) == 1
 
 
-def test_energy_reporting(mocker, mock_inquirer):
+def test_energy_reporting(mocker, mock_inquirer, console_jobs):
     """Include energy reporting with the `energy` format code."""
     mocker.patch("reportseff.console.which", return_value=True)
     runner = CliRunner()
     sub_result = mocker.MagicMock()
     sub_result.returncode = 0
     sub_result.stdout = (
-        "^|^32^|^00:01:09^|^37403870_1^|^37403937^|^^|^1^|^32000M^|^"
+        "^|^32^|^00:01:09^|^37403870_1^|^37403937^|^^|^1^|^1^|^32000M^|^"
         "COMPLETED^|^^|^00:02:00^|^00:47.734\n"
-        "^|^32^|^00:01:09^|^37403870_1.batch^|^37403937.batch^|^6300K^|^1^|^^|^"
+        "^|^32^|^00:01:09^|^37403870_1.batch^|^37403937.batch^|^6300K^|^1^|^1^|^^|^"
         "COMPLETED^|^energy=33,fs/disk=0^|^^|^00:47.733\n"
-        "^|^32^|^00:01:09^|^37403870_1.extern^|^37403937.extern^|^4312K^|^1^|^^|^"
+        "^|^32^|^00:01:09^|^37403870_1.extern^|^37403937.extern^|^4312K^|^1^|^1^|^^|^"
         "COMPLETED^|^energy=33,fs/disk=0^|^^|^00:00.001\n"
-        "^|^32^|^00:01:21^|^37403870_2^|^37403938^|^^|^1^|^32000M^|^"
+        "^|^32^|^00:01:21^|^37403870_2^|^37403938^|^^|^1^|^1^|^32000M^|^"
         "COMPLETED^|^^|^00:02:00^|^00:41.211\n"
-        "^|^32^|^00:01:21^|^37403870_2.batch^|^37403938.batch^|^6316K^|^1^|^^|^"
+        "^|^32^|^00:01:21^|^37403870_2.batch^|^37403938.batch^|^6316K^|^1^|^1^|^^|^"
         "COMPLETED^|^energy=32,fs/disk=0^|^^|^00:41.210\n"
-        "^|^32^|^00:01:21^|^37403870_2.extern^|^37403938.extern^|^4312K^|^1^|^^|^"
+        "^|^32^|^00:01:21^|^37403870_2.extern^|^37403938.extern^|^4312K^|^1^|^1^|^^|^"
         "COMPLETED^|^energy=32,fs/disk=0^|^^|^00:00:00\n"
-        "^|^32^|^00:01:34^|^37403870_3^|^37403939^|^^|^1^|^32000M^|^"
+        "^|^32^|^00:01:34^|^37403870_3^|^37403939^|^^|^1^|^1^|^32000M^|^"
         "COMPLETED^|^^|^00:02:00^|^00:51.669\n"
-        "^|^32^|^00:01:34^|^37403870_3.batch^|^37403939.batch^|^6184K^|^1^|^^|^"
+        "^|^32^|^00:01:34^|^37403870_3.batch^|^37403939.batch^|^6184K^|^1^|^1^|^^|^"
         "COMPLETED^|^energy=30,fs/disk=0^|^^|^00:51.667\n"
-        "^|^32^|^00:01:35^|^37403870_3.extern^|^37403939.extern^|^4312K^|^1^|^^|^"
+        "^|^32^|^00:01:35^|^37403870_3.extern^|^37403939.extern^|^4312K^|^1^|^1^|^^|^"
         "COMPLETED^|^fs/disk=0,energy=30^|^^|^00:00.001\n"
-        "^|^32^|^00:01:11^|^37403870_4^|^37403870^|^^|^1^|^32000M^|^"
+        "^|^32^|^00:01:11^|^37403870_4^|^37403870^|^^|^1^|^1^|^32000M^|^"
         "COMPLETED^|^^|^00:02:00^|^01:38.184\n"
-        "^|^32^|^00:01:11^|^37403870_4.batch^|^37403870.batch^|^6300K^|^1^|^^|^"
+        "^|^32^|^00:01:11^|^37403870_4.batch^|^37403870.batch^|^6300K^|^1^|^1^|^^|^"
         "COMPLETED^|^fs/disk=0^|^^|^01:38.183\n"
-        "^|^32^|^00:01:11^|^37403870_4.extern^|^37403870.extern^|^4312K^|^1^|^^|^"
+        "^|^32^|^00:01:11^|^37403870_4.extern^|^37403870.extern^|^4312K^|^1^|^1^|^^|^"
         "COMPLETED^|^energy=27,fs/disk=0^|^^|^00:00.001\n"
     )
     mocker.patch("reportseff.db_inquirer.subprocess.run", return_value=sub_result)
@@ -1028,7 +850,7 @@ def test_energy_reporting(mocker, mock_inquirer):
     assert len(output) == 5
 
 
-def test_extra_args(mocker, mock_inquirer):
+def test_extra_args(mocker, mock_inquirer, console_jobs):
     """Can add extra arguments for sacct."""
     mocker.patch("reportseff.console.which", return_value=True)
     runner = CliRunner()