# 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