code: def compute_with_trace(*args):
      
      # 1. Create top-level Eliot Action:
      with start_action(action_type="dask:compute"):
          # In order to reduce logging verbosity, add logging to the already
          # optimized graph:
          optimized = optimize(*args, optimizations=[_add_logging])
          return compute(*optimized, optimize_graph=False)
docs: Do Dask compute(), but with added Eliot tracing.
  
      Dask is a graph of tasks, but Eliot logs trees.  So we need to emulate a
      graph using a tree.  We do this by making Eliot action for each task, but
      having it list the tasks it depends on.
  
      We use the following algorithm:
  
          1. Create a top-level action.
  
          2. For each entry in the dask graph, create a child with
             serialize_task_id.  Do this in likely order of execution, so that
             if B depends on A the task level of B is higher than the task Ievel
             of A.
  
          3. Replace each function with a wrapper that uses the corresponding
             task ID (with Action.continue_task), and while it's at it also
             records which other things this function depends on.
  
      Known issues:
  
          1. Retries will confuse Eliot.  Probably need different
             distributed-tree mechanism within Eliot to solve that.