blob: 571dc6541c9edb588aecb8306c57fc69f3eb8e05 [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
import logging
import os
import re
try:
from elftools.elf.elffile import ELFFile
except ImportError as e:
# Handle pre-python2.7s' lack of collections.OrderedDict, which we include in
# impala-python as ordereddict.OrderedDict.
if 'cannot import name OrderedDict' == str(e):
import monkeypatch
from ordereddict import OrderedDict
monkeypatch.patch(OrderedDict, 'collections', 'OrderedDict')
from elftools.elf.elffile import ELFFile
else:
raise e
LOG = logging.getLogger('tests.common.environ')
test_start_cluster_args = os.environ.get("TEST_START_CLUSTER_ARGS", "")
# Find the likely BuildType of the running Impala. Assume it's found through the path
# $IMPALA_HOME/be/build/latest as a fallback.
impala_home = os.environ.get("IMPALA_HOME", "")
build_type_arg_regex = re.compile(r'--build_type=(\w+)', re.I)
build_type_arg_search_result = re.search(build_type_arg_regex, test_start_cluster_args)
if build_type_arg_search_result is not None:
build_type_dir = build_type_search_result.groups()[0].lower()
else:
build_type_dir = 'latest'
# Resolve any symlinks in the path.
impalad_basedir = \
os.path.realpath(os.path.join(impala_home, 'be/build', build_type_dir)).rstrip('/')
IMPALAD_PATH = os.path.join(impalad_basedir, 'service', 'impalad')
class SpecificImpaladBuildTypes:
"""
Represent a specific build type. In reality, there 5 specific build types. These
specific build types are needed by Python test code.
The specific build types and their *most distinguishing* compiler options are:
1. ADDRESS_SANITIZER (clang -fsanitize=address)
2. DEBUG (gcc -ggdb)
3. DEBUG_CODE_COVERAGE (gcc -ggdb -ftest-coverage)
4. RELEASE (gcc)
5. RELEASE_CODE_COVERAGE (gcc -ftest-coverage)
"""
# ./buildall.sh -asan
ADDRESS_SANITIZER = 'address_sanitizer'
# ./buildall.sh
DEBUG = 'debug'
# ./buildall.sh -codecoverage
DEBUG_CODE_COVERAGE = 'debug_code_coverage'
# ./buildall.sh -release
RELEASE = 'release'
# ./buildall.sh -release -codecoverage
RELEASE_CODE_COVERAGE = 'release_code_coverage'
class ImpaladBuild(object):
"""
Acquires and provides characteristics about the way the Impala under test was compiled
and its likely effects on its responsiveness to automated test timings.
"""
def __init__(self, impalad_path):
self.impalad_path = impalad_path
die_name, die_producer = self._get_impalad_dwarf_info()
self._set_impalad_build_type(die_name, die_producer)
@property
def specific_build_type(self):
"""
Return the correct SpecificImpaladBuildTypes for the Impala under test.
"""
return self._specific_build_type
def has_code_coverage(self):
"""
Return whether the Impala under test was compiled with code coverage enabled.
"""
return self.specific_build_type in (SpecificImpaladBuildTypes.DEBUG_CODE_COVERAGE,
SpecificImpaladBuildTypes.RELEASE_CODE_COVERAGE)
def is_asan(self):
"""
Return whether the Impala under test was compiled with ASAN.
"""
return self.specific_build_type == SpecificImpaladBuildTypes.ADDRESS_SANITIZER
def is_dev(self):
"""
Return whether the Impala under test is a development build (i.e., any debug or ASAN
build).
"""
return self.specific_build_type in (
SpecificImpaladBuildTypes.ADDRESS_SANITIZER, SpecificImpaladBuildTypes.DEBUG,
SpecificImpaladBuildTypes.DEBUG_CODE_COVERAGE)
def runs_slowly(self):
"""
Return whether the Impala under test "runs slowly". For our purposes this means
either compiled with code coverage enabled or ASAN.
"""
return self.has_code_coverage() or self.is_asan()
def _get_impalad_dwarf_info(self):
"""
Read the impalad_path ELF binary, which is supposed to contain DWARF, and read the
DWARF to understand the compiler options. Return a 2-tuple of the two useful DIE
attributes of the first compile unit: the DW_AT_name and DW_AT_producer. If
something goes wrong doing this, log a warning and return nothing.
"""
# Some useful references:
# - be/CMakeLists.txt
# - gcc(1), especially -grecord-gcc-switches, -g, -ggdb, -gdwarf-2
# - readelf(1)
# - general reading about DWARF
# A useful command for exploration without having to wade through many bytes is:
# readelf --debug-dump=info --dwarf-depth=1 impalad
# The DWARF lines are long, raw, and nasty; I'm hesitant to paste them here, so
# curious readers are highly encouraged to try the above, or read IMPALA-3501.
die_name = None
die_producer = None
try:
with open(self.impalad_path, 'rb') as fh:
impalad_elf = ELFFile(fh)
if impalad_elf.has_dwarf_info():
dwarf_info = impalad_elf.get_dwarf_info()
# We only need the first CU, hence the unconventional use of the iterator
# protocol.
cu_iterator = dwarf_info.iter_CUs()
first_cu = next(cu_iterator)
top_die = first_cu.get_top_DIE()
die_name = top_die.attributes['DW_AT_name'].value
die_producer = top_die.attributes['DW_AT_producer'].value
except Exception as e:
LOG.warn('Failure to read DWARF info from {0}: {1}'.format(self.impalad_path,
str(e)))
return die_name, die_producer
def _set_impalad_build_type(self, die_name, die_producer):
"""
Use a heuristic based on the DW_AT_producer and DW_AT_name of the first compile
unit, as returned by _get_impalad_dwarf_info(), to figure out which of 5 supported
builds of impalad we're dealing with. If the heuristic can't determine, fall back to
assuming a debug build and log a warning.
"""
ASAN_CU_NAME = 'asan_preinit.cc'
NON_ASAN_CU_NAME = 'daemon-main.cc'
GDB_FLAG = '-ggdb'
CODE_COVERAGE_FLAG = '-ftest-coverage'
if die_name is None or die_producer is None:
LOG.warn('Not enough DWARF info in {0} to determine build type; choosing '
'DEBUG'.format(self.impalad_path))
self._specific_build_type = SpecificImpaladBuildTypes.DEBUG
return
is_debug = GDB_FLAG in die_producer
specific_build_type = SpecificImpaladBuildTypes.DEBUG
if die_name.endswith(ASAN_CU_NAME):
specific_build_type = SpecificImpaladBuildTypes.ADDRESS_SANITIZER
elif not die_name.endswith(NON_ASAN_CU_NAME):
LOG.warn('Unexpected DW_AT_name in first CU: {0}; choosing '
'DEBUG'.format(die_name))
specific_build_type = SpecificImpaladBuildTypes.DEBUG
elif CODE_COVERAGE_FLAG in die_producer:
if is_debug:
specific_build_type = SpecificImpaladBuildTypes.DEBUG_CODE_COVERAGE
else:
specific_build_type = SpecificImpaladBuildTypes.RELEASE_CODE_COVERAGE
else:
if is_debug:
specific_build_type = SpecificImpaladBuildTypes.DEBUG
else:
specific_build_type = SpecificImpaladBuildTypes.RELEASE
self._specific_build_type = specific_build_type
IMPALAD_BUILD = ImpaladBuild(IMPALAD_PATH)
def specific_build_type_timeout(
default_timeout, slow_build_timeout=None, asan_build_timeout=None,
code_coverage_build_timeout=None):
"""
Return a test environment-specific timeout based on the sort of
SpecificImpalaBuildType under test.
Required parameter: default_timeout - default timeout value. This applies when Impala is
a standard release or debug build, or if no other timeouts are specified.
Optional parameters:
slow_build_timeout - timeout to use if we're running against *any* build known to be
slow. If specified, this will preempt default_timeout if Impala is expected to be
"slow". You can use this as a shorthand in lieu of specifying all of the following
parameters.
The parameters below correspond to specific build types. These preempt both
slow_build_timeout and default_timeout, if the Impala under test is a build of the
applicable type:
asan_build_timeout - timeout to use if Impala with ASAN is running
code_coverage_build_timeout - timeout to use if Impala with code coverage is running
(both debug and release code coverage)
"""
if IMPALAD_BUILD.is_asan() and asan_build_timeout is not None:
timeout_val = asan_build_timeout
elif IMPALAD_BUILD.has_code_coverage() and code_coverage_build_timeout is not None:
timeout_val = code_coverage_build_timeout
elif IMPALAD_BUILD.runs_slowly() and slow_build_timeout is not None:
timeout_val = slow_build_timeout
else:
timeout_val = default_timeout
return timeout_val