def StoreAndCompressUserdata()

in tools/android/emulator/emulated_device.py [0:0]


  def StoreAndCompressUserdata(self, location, ram_binary_location=None):
    """Stores the emulator's userdata files."""
    assert not self._running, 'Emulator is still running.'
    assert self._images_dir, 'Emulator never started.'
    assert not self._child_will_delete_tmp, 'Emulator is deleting tmp dir.'

    if not os.path.exists(os.path.dirname(location)):
      os.makedirs(os.path.dirname(location))
    logging.info('Storing emulator state to: %s', location)

    image_files = [
        self._UserdataQemuFile() + self._PossibleImgSuffix(),
        self._CacheFile() + self._PossibleImgSuffix(),
        self._SdcardFile() + self._PossibleImgSuffix(),
        self._SnapshotFile(),
        self._RamdiskFile()]

    encryption_qcow = self._EncryptionKeyImageFile() + self._PossibleImgSuffix()
    if os.path.exists(encryption_qcow):
      image_files.append(encryption_qcow)

    if os.path.exists(self._VendorFile()):
      image_files.append(self._VendorFile() + self._PossibleImgSuffix())

    if self.GetApiVersion() >= 28:
      image_files.append(self._UserdataQemuFile())

    if (self._metadata_pb.emulator_type ==
        emulator_meta_data_pb2.EmulatorMetaDataPb.QEMU2):
      image_files.append(
          os.path.join(self._SessionImagesDir(), 'version_num.cache'))
      image_files.append(self._SystemFile() + self._PossibleImgSuffix())

    # The snapshots directory if it is successfully generated is of the
    # following directory structure
    # |- snapshots
    # |   |- default_boot
    # |   |   |- hardware.ini
    # |   |   |- ram.bin
    # |   |   |- snapshot.pb
    # |   |   |- textures.bin
    # |   |- snapshot_deps.pb
    # Of all the files the snapshot feature creates, ram.bin is the file that's
    # the largest and instead of wrapping up ram.bin within the userdata.tar
    # file, we explicitly emit out a separate file so that we don't waste
    # cycles in untarring the huge file and using tmpfs space during the start
    # cycle. ram.bin is symlinked to the file path that are passed as 
    # inputs. So all the other files are tarred up in the userdata.tar.gz file
    # and ram.bin is explicitly copied over as a output file.
    snapshot_file_found = False
    for r, _, f in os.walk(os.path.join(self._SessionImagesDir(), 'snapshots')):
      for each_file in f:
        if each_file == 'ram.bin' and ram_binary_location:
          shutil.copy(os.path.join(r, each_file), ram_binary_location)
          snapshot_file_found = True
          continue
        image_files.append(os.path.join(r, each_file))

    # TODO: Instead of failing and causing BUILD failures, I think we
    # should probably return back a empty file and subsequent loads would just
    # do regular boots since they don't get build failures. We should record
    # it and find out how often that happens though.
    if ram_binary_location and not snapshot_file_found:
      raise Exception('Requested to save snapshots but didnt find ram.bin')

    # Before compressing make sure none of the files are being modified since
    # the Kill command is not really synchronous. The best way to deal with that
    # sitation is either wait for the Emulator process to die and since we use
    # os.fork, we might have to pass around the emulator pid back to the parent
    # process. The easiest thing to do would be to lsof all the files that we
    # are tarring and that'll guarantee that no other process is writing to
    # those files before we are really shutdown.
    # Hopefully with v2 design, we don't have to fork.
    all_files_closed = False
    lsof_command = ['/usr/bin/lsof'] + image_files
    # Wait for 10 seconds before giving up.
    for _ in range(10):
      try:
        output = subprocess.check_output(lsof_command)
        logging.info('lsof output :%s', output)
      except subprocess.CalledProcessError as err:
        # If no processes are writing to it, then we are done and it will throw
        # a exception.
        if err.returncode == 1:
          all_files_closed = True
          break
      time.sleep(1)

    image_files = ['./%s' % os.path.relpath(f, self._images_dir)
                   for f in image_files]

    if not all_files_closed:
      raise Exception('Emulator still not dead after issuing KILL and waiting '
                      '10 seconds')

    if (self._metadata_pb.emulator_type ==
        emulator_meta_data_pb2.EmulatorMetaDataPb.QEMU2):
      # QEMU2 uses .qcow disk images which are diffs over the original
      # userdata.img. Therefore they're already quite small and the
      # format will not compress very well. (roughly 2x vs RAW images which
      # compress 4x or better).
      # so running thru gzip is slow and doesn't save much space.
      subprocess.check_call([
          'tar',
          '-cSpf',
          location,
          '-C',
          self._images_dir] + image_files)
      logging.info('Tar/gz pipeline completes.')
    else:
      with open(location, 'w') as dat_file:
        tar_proc = subprocess.Popen(
            ['tar', '-cSp', '-C', self._images_dir] + image_files,
            stdout=subprocess.PIPE)
        # consider replacing with zippy?
        gz_proc = subprocess.Popen(
            ['gzip'],
            stdin=tar_proc.stdout,
            stdout=dat_file)
        tar_proc.stdout.close()  # tar will get a SIGPIPE if gz dies.
        gz_ret = gz_proc.wait()
        tar_ret = tar_proc.wait()
        assert gz_ret == 0 and tar_ret == 0, 'gz: %d tar: %d' % (gz_ret,
                                                                 tar_ret)
        logging.info('Tar/gz pipeline completes.')