# Copyright 2017 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """This throttler tries to reduce result size by compress files to tgz file. """ import re import os import tarfile import throttler_lib import utils_lib # File with extensions that can not be zipped or compression won't reduce file # size further. UNZIPPABLE_EXTENSIONS = set([ '.gz', '.jpg', '.png', '.tgz', '.xz', '.zip', ]) # Regex for files that should not be compressed. UNZIPPABLE_FILE_PATTERNS = [ 'BUILD_INFO-.*' # ACTS test result files. ] # Default threshold of file size in byte for it to be qualified for compression. # Files smaller than the threshold will not be compressed. DEFAULT_FILE_SIZE_THRESHOLD_BYTE = 100 * 1024 def _zip_file(file_info): """Zip the file to reduce the file size. @param file_info: A ResultInfo object containing summary for the file to be shrunk. """ utils_lib.LOG('Compressing file %s' % file_info.path) parent_result_info = file_info.parent_result_info new_name = file_info.name + '.tgz' new_path = os.path.join(os.path.dirname(file_info.path), new_name) if os.path.exists(new_path): utils_lib.LOG('File %s already exists, removing...' % new_path) if not throttler_lib.try_delete_file_on_disk(new_path): return parent_result_info.remove_file(new_name) with tarfile.open(new_path, 'w:gz') as tar: tar.add(file_info.path, arcname=os.path.basename(file_info.path)) stat = os.stat(file_info.path) if not throttler_lib.try_delete_file_on_disk(file_info.path): # Clean up the intermediate file. throttler_lib.try_delete_file_on_disk(new_path) utils_lib.LOG('Failed to compress %s' % file_info.path) return # Modify the new file's timestamp to the old one. os.utime(new_path, (stat.st_atime, stat.st_mtime)) # Get the original file size before compression. original_size = file_info.original_size parent_result_info.remove_file(file_info.name) parent_result_info.add_file(new_name) new_file_info = parent_result_info.get_file(new_name) # Set the original size to be the size before compression. new_file_info.original_size = original_size # Set the trimmed size to be the physical file size of the compressed file. new_file_info.trimmed_size = new_file_info.size def _get_zippable_files(file_infos, file_size_threshold_byte): """Filter the files that can be throttled. @param file_infos: A list of ResultInfo objects. @param file_size_threshold_byte: Threshold of file size in byte for it to be qualified for compression. @yield: ResultInfo objects that can be shrunk. """ for info in file_infos: ext = os.path.splitext(info.name)[1].lower() if ext in UNZIPPABLE_EXTENSIONS: continue match_found = False for pattern in UNZIPPABLE_FILE_PATTERNS: if re.match(pattern, info.name): match_found = True break if match_found: continue if info.trimmed_size <= file_size_threshold_byte: continue yield info def throttle(summary, max_result_size_KB, file_size_threshold_byte=DEFAULT_FILE_SIZE_THRESHOLD_BYTE, skip_autotest_log=False): """Throttle the files in summary by compressing file. Stop throttling until all files are processed or the result file size is already reduced to be under the given max_result_size_KB. @param summary: A ResultInfo object containing result summary. @param max_result_size_KB: Maximum test result size in KB. @param file_size_threshold_byte: Threshold of file size in byte for it to be qualified for compression. @param skip_autotest_log: True to skip shrink Autotest logs, default is False. """ file_infos, _ = throttler_lib.sort_result_files(summary) extra_patterns = ([throttler_lib.AUTOTEST_LOG_PATTERN] if skip_autotest_log else []) file_infos = throttler_lib.get_throttleable_files( file_infos, extra_patterns) file_infos = _get_zippable_files(file_infos, file_size_threshold_byte) for info in file_infos: _zip_file(info) if throttler_lib.check_throttle_limit(summary, max_result_size_KB): return