docs: def callprint(self, cmd, shell=None, lock_file=None, nofail=False, container=None):
          """
          Prints the command, and then executes it, then prints the memory use and
          return code of the command.
  
          Uses python's subprocess.Popen() to execute the given command. The shell argument is simply
          passed along to Popen(). You should use shell=False (default) where possible, because this enables memory
          profiling. You should use shell=True if you require shell functions like redirects (>) or stars (*), but this
          will prevent the script from monitoring memory use. The pipes (|) will be used to split the command into
          subprocesses run within python, so the memory profiling is possible.
  
          cmd can also be a series (a dict object) of multiple commands, which will be run in succession.
  
          :param str | Iterable[str] cmd: Bash command(s) to be run.
          :param str lock_file: a lock file name
          :param bool nofail: Should the pipeline bail on a nonzero return from a process? Default: False
              Nofail can be used to implement non-essential parts of the pipeline; if these processes fail,
              they will not cause the pipeline to bail out.
          :param bool shell: if the command should be run it its own shell, default: None (will try
              to determine based on the command)
          :param container: Named Docker container in which to execute.
          :param container: str
          """
          # The Popen shell argument works like this:
          # if shell=False, then we format the command (with split()) to be a list of command and its arguments.
          # Split the command to use shell=False;
          # leave it together to use shell=True;
  
          def get_mem_child_sum(proc):
              try:
                  # get children processes
                  children = proc.children(recursive=True)
                  # get RSS memory of each child proc and sum all
                  mem_sum = sum([x.memory_info().rss for x in children])
                  # return in gigs
                  return mem_sum/1e9
              except (psutil.NoSuchProcess, psutil.ZombieProcess) as e:
                  print(e)
                  print("Warning: couldn't add memory use for process: {}".format(proc.pid))
                  return 0
  
  
          def display_memory(memval):
              return None if memval < 0 else "{}GB".format(round(memval, 3))
  
          def make_dict(command):
              a, s = (command, True) if check_shell(command, shell) else (shlex.split(command), False)
              return dict(args=a, stdout=subprocess.PIPE, shell=s)
  
          if container:
              cmd = "docker exec " + container + " " + cmd
  
          param_list = [make_dict(c) for c in split_by_pipes(cmd)] \
              if check_shell_pipes(cmd) else [dict(args=cmd, stdout=None, shell=True)]
  
          proc_name = get_proc_name(cmd)
  
          # stop_times = []
          processes = []
          running_processes = []
          start_time = time.time()
          for i in range(len(param_list)):
              running_processes.append(i)
              if i == 0:
                  processes.append(psutil.Popen(preexec_fn=os.setpgrp, **param_list[i]))
              else:
                  param_list[i]["stdin"] = processes[i - 1].stdout
                  processes.append(psutil.Popen(preexec_fn=os.setpgrp, **param_list[i]))
  
              self.procs[processes[-1].pid] = {
                  "proc_name": proc_name,
                  "subproc_name" : get_proc_name(param_list[i]["args"]),
                  "start_time": start_time,
                  "container": container,
                  "p": processes[-1]
              }
  
          self._report_command(cmd, [x.pid for x in processes])
              # Capture the subprocess output in <pre> tags to make it format nicely
              # if the markdown log file is displayed as HTML.
          print("<pre>")
  
          local_maxmems = [-1] * len(running_processes)
          returncodes = [None] * len(running_processes)
  
          if not self.wait:
              print("</pre>")
              ids = [x.pid for x in processes]
              print ("Not waiting for subprocess(es): " + str(ids))
              return [0, -1]
  
          def proc_wrapup(i):
              """
              :param i: internal ID number of the subprocess
              """
              returncode = processes[i].returncode
              current_pid = processes[i].pid
  
              info = "Process {pid} returned {ret}; memory: {mem}. ".format(
                  pid=current_pid, 
                  ret=processes[i].returncode,
                  mem=display_memory(local_maxmems[i]))
              
              # report process profile
              self._report_profile(self.procs[current_pid]["proc_name"], lock_file, time.time() - self.procs[current_pid]["start_time"], local_maxmems[i])
  
              # Remove this as a running subprocess
              del self.procs[current_pid]
              running_processes.remove(i)
  
              returncodes[i] = returncode
              return info
  
  
          sleeptime = .0001
          info = ""
          while running_processes:
              for i in running_processes:
                  local_maxmems[i] = max(local_maxmems[i], (get_mem_child_sum(processes[i])))
                  self.peak_memory = max(self.peak_memory, local_maxmems[i])
                  if not self._attend_process(processes[i], sleeptime):
                      info += proc_wrapup(i)
  
              # the sleeptime is extremely short at the beginning and gets longer exponentially 
              # (+ constant to prevent copious checks at the very beginning)
              # = more precise mem tracing for short processes
              sleeptime = min((sleeptime + 0.25) * 3 , 60/len(processes))
  
          # All jobs are done, print a final closing and job info
          stop_time = time.time()
          info += " Elapsed: " + str(datetime.timedelta(seconds=self.time_elapsed(start_time))) + "."
          info += " Peak memory: {pipe}.".format(pipe=display_memory(self.peak_memory))
  
          print("</pre>")
          print(info)
  
          for rc in returncodes:
              if rc != 0:
                  msg = "Subprocess returned nonzero result. Check above output for details"
                  self._triage_error(SubprocessError(msg), nofail)
  
  
  
          return [returncodes, local_maxmems]