docs: def add_metadata(self, observation, info, available_at=None):
          """Extract metadata from a pixel observation and add it to the info
          """
          observation = observation['vision']
          if observation is None: return
          if self.network is not None and not self.network.active():
              return
          elif self.metadata_decoder is None:
              return
          elif observation is None:
              return
          # should return a dict with now/probe_received_at keys
          with pyprofile.push('vnc_env.diagnostics.DiagnosticsInstance.add_metadata.decode'):
              metadata = self.metadata_decoder.decode(observation, available_at=available_at)
  
          if metadata is False:
              # No metadata ready, though it doesn't mean parsing failed
              metadata = None
          elif metadata is None:
              if self.could_read_metadata:
                  self.could_read_metadata = False
                  extra_logger.info('[%s] Stopped being able to read metadata (expected when environment resets)', self.label)
          elif not self.could_read_metadata:
              self.could_read_metadata = True
              extra_logger.info('[%s] Started being able to read metadata', self.label)
  
          if self.metadata_decoder.flag_synchronous and metadata is not None:
              info['diagnostics.image_remote_time'] = metadata['now']
  
          local_now = time.time()
  
          if self.network is None:
              # Assume the clock skew is zero. Should only be run on the
              # same machine as the VNC server, such as the universe
              # instance inside of the environmenth containers.
              real_clock_skew = self.zero_clock_skew
          else:
              # Note: this is a 2-length vector of (min, max), so anything added to
              # it is also going to be a 2-length vector.
              # Most of the diagnostics below are, but you have to look carefully.
              real_clock_skew = self.network.reversed_clock_skew()
  
          # Store real clock skew here
          info['stats.gauges.diagnostics.clock_skew'] = real_clock_skew
          if self.ignore_clock_skew:
              clock_skew = self.zero_clock_skew
          else:
              clock_skew = real_clock_skew
  
          if metadata is not None:
              # We'll generally update the observation timestamp infrequently
              if self.last_observation_timestamp == metadata['now']:
                  delta = None
              else:
                  # We just got a new timestamp in the observation!
                  self.last_observation_timestamp = metadata['now']
                  observation_now = metadata['now']
                  delta = observation_now - metadata['available_at']
  
                  # Subtract *local* time it was received from the *remote* time
                  # displayed. Negate and reverse order to fix time ordering.
                  info['stats.gauges.diagnostics.lag.observation'] = -(delta + clock_skew)[[1, 0]]
  
              # if self.network is None:
              #     # The rest of diagnostics need the network, so we're done here
              #     return
  
              probe_received_at = metadata['probe_received_at']
              if probe_received_at == 0 or self.disable_action_probes:
                  # Happens when the env first starts
                  self.probe_received_at = None
              elif self.probe_received_at is None: # this also would work for the equality case
                  self.probe_received_at = probe_received_at
              elif self.probe_received_at != probe_received_at and self.probe_sent_at is None:
                  logger.info('[%s] Probe is marked as received at %s, but probe_sent_at is None. This is surprising. (HINT: do you have multiple universe instances talking to the same environment?)', self.label, probe_received_at)
              elif self.probe_received_at != probe_received_at:
                  extra_logger.debug('[%s] Next probe received: old=%s new=%s', self.label, self.probe_received_at, probe_received_at)
                  self.probe_received_at = probe_received_at
                  # Subtract the *local* time we sent it from the *remote* time it was received
                  self.action_latency_skewed = probe_received_at - self.probe_sent_at
                  self.probe_sent_at = None
  
              if self.action_latency_skewed:
                  action_lag = self.action_latency_skewed + clock_skew
                  self.action_latency_skewed = None
              else:
                  action_lag = None
              info['stats.gauges.diagnostics.lag.action'] = action_lag
  
          local_now = time.time()
          # Look at when the remote believed it parsed the score (not
          # all envs send this currently).
          #
          # Also, if we received no new rewards, then this values is
          # None. This could indicate a high reward latency (bad,
          # uncommon), or that the agent is calling step faster than new
          # rewards are coming in (good, common).
          remote_score_now = info.get('rewarder.lag.observation.timestamp')
          if remote_score_now is not None:
              delta = remote_score_now - local_now
              info['stats.gauges.diagnostics.lag.reward'] = -(delta + clock_skew)[[1, 0]]
  
          # Look at when the remote send the message, so we know how
          # long it's taking for messages to get to us.
          rewarder_message_now = info.get('reward_buffer.remote_time')
          if rewarder_message_now:
              delta = rewarder_message_now - local_now
              info['stats.gauges.diagnostics.lag.rewarder_message'] = -(delta + clock_skew)[[1, 0]]