(self, cursor, statement, schema, obfuscated_statement, query_signature)
return None, error_states
Tries the available methods used to explain a statement for the given schema. If a non-retryable
        error occurs (such as a permissions error), then statements executed under the schema will be
        disallowed in future attempts.
        returns: An execution plan, otherwise it returns a list of error ExplainStates
        rtype: Optional[Dict], List[ExplainState]
Tries the available methods used to explain a statement for the given schema. If a non-retryable
        error occurs (such as a permissions error), then statements executed under the schema will be
        disallowed in future attempts.
        returns: An execution plan, otherwise it returns a list of error ExplainStates
        rtype: Optional[Dict], List[ExplainState]
Tries the available methods used to explain a statement for the given schema . If a non - retryable error occurs ( such as a permissions error ) then statements executed under the schema will be disallowed in future attempts . returns : An execution plan otherwise it returns a list of error ExplainStates rtype : Optional [ Dict ] List [ ExplainState ]
def _explain_statement(self, cursor, statement, schema, obfuscated_statement, query_signature):
        """
        Tries the available methods used to explain a statement for the given schema. If a non-retryable
        error occurs (such as a permissions error), then statements executed under the schema will be
        disallowed in future attempts.
        returns: An execution plan, otherwise it returns a list of error ExplainStates
        rtype: Optional[Dict], List[ExplainState]
        """
        if self._get_truncation_state(statement) == StatementTruncationState.truncated:
            error_state = ExplainState(
                strategy=None,
                error_code=DBExplainErrorCode.query_truncated,
                error_message='truncated length: {}'.format(len(statement)),
            )
            return None, [error_state]

        if not self._can_explain(obfuscated_statement):
            self._log.debug('Skipping statement which cannot be explained: %s', obfuscated_statement)
            error_state = ExplainState(
                strategy=None, error_code=DBExplainErrorCode.no_plans_possible, error_message=None
            )
            return None, [error_state]

        # for caching an ExplainState (whether error or OK) per schema
        explain_state_cache_key = 'explain_state:%s' % schema

        # for caching a list of *error* ExplainStates per query
        query_cache_key = (schema, query_signature)

        start_time = time.time()
        self._log.debug('explaining statement. schema=%s, statement="%s"', schema, obfuscated_statement)
        error_state = self._use_schema(cursor, schema, explain_state_cache_key)
        if error_state:
            self._log.debug(
                'Failed to collect execution plan because schema could not be accessed. schema=%s error=%s: %s',
                schema,
                error_state,
                obfuscated_statement,
            )
            return None, [error_state]

        preferred_strategies = list(self._preferred_explain_strategies)
        explain_state = self._collection_strategy_cache.get(explain_state_cache_key, EMPTY_EXPLAIN_STATE)

        # if we've cached a successful non-error strategy, put it at the front of the list
        if explain_state.strategy and not explain_state.error_code:
            preferred_strategies.remove(explain_state.strategy)
            preferred_strategies.insert(0, explain_state.strategy)

        # if there is a default schema on the connection then the only way we can guarantee collection of plans is
        # by having that same default schema set when invoking the EXPLAIN, which is what we do with the "PROCEDURE"
        # strategy. If there is no default schema then we can collect it from anywhere, typically FQ_PROCEDURE, which
        # is expected to be in the dedicated datadog schema.
        optimal_strategy = "PROCEDURE" if schema else "FQ_PROCEDURE"
        is_optimal_strategy_cached = explain_state.strategy == optimal_strategy and not explain_state.error_code
        if is_optimal_strategy_cached:
            # if the optimal strategy was cached then that means at least one recent query must have been successfully
            # collected from this schema and therefore the schema is probably setup OK.
            # knowing that the schema is setup OK, if we fail to collect an execution plan for a given query then
            # it's likely a problem with that specific query and it's not worth trying it again so we cache those
            # errors for a longer time to avoid excessive futile explain attempts.
            cached_error_states = self._explain_error_states_cache.get(query_cache_key, None)
            if cached_error_states:
                self._log.debug(
                    'Skipping execution plan collection for query due to cached failures %s: %s',
                    cached_error_states,
                    obfuscated_statement,
                )
                return None, cached_error_states

        error_states = []
        for strategy in preferred_strategies:
            if not schema and strategy == "PROCEDURE":
                self._log.debug(
                    'skipping PROCEDURE strategy as there is no default schema for this statement="%s"',
                    obfuscated_statement,
                )
                continue
            try:
                plan = self._explain_strategies[strategy](cursor, statement, obfuscated_statement)
                if plan:
                    self._collection_strategy_cache[explain_state_cache_key] = ExplainState(
                        strategy=strategy, error_code=None, error_message=None
                    )
                    self._log.debug(
                        'Successfully collected execution plan. strategy=%s, schema=%s, statement="%s"',
                        strategy,
                        schema,
                        obfuscated_statement,
                    )
                    self._check.histogram(
                        "dd.mysql.run_explain.time",
                        (time.time() - start_time) * 1000,
                        tags=self._tags + ["strategy:{}".format(strategy)],
                        hostname=self._check.resolved_hostname,
                    )
                    return plan, None
            except pymysql.err.DatabaseError as e:
                if len(e.args) != 2:
                    raise
                error_state = ExplainState(
                    strategy=strategy, error_code=DBExplainErrorCode.database_error, error_message=str(type(e))
                )
                error_states.append(error_state)
                self._log.debug(
                    'Failed to collect execution plan. error=%s, strategy=%s, schema=%s, statement="%s"',
                    e.args,
                    strategy,
                    schema,
                    obfuscated_statement,
                )
                continue

        if is_optimal_strategy_cached and error_states:
            self._log.debug(
                'Caching execution plan failure for query to skip future explains %s: %s',
                error_states,
                obfuscated_statement,
            )
            self._explain_error_states_cache[query_cache_key] = error_states

        return None, error_states
def _explain_statement ( self , cursor , statement , schema , obfuscated_statement , query_signature ) : if self . _get_truncation_state ( statement ) == StatementTruncationState . truncated : error_state = ExplainState ( strategy = None , error_code = DBExplainErrorCode . query_truncated , error_message = 'truncated length: {}' . format ( len ( statement ) ) , ) return None , [ error_state ] if not self . _can_explain ( obfuscated_statement ) : self . _log . debug ( 'Skipping statement which cannot be explained: %s' , obfuscated_statement ) error_state = ExplainState ( strategy = None , error_code = DBExplainErrorCode . no_plans_possible , error_message = None ) return None , [ error_state ] # for caching an ExplainState (whether error or OK) per schema explain_state_cache_key = 'explain_state:%s' % schema # for caching a list of *error* ExplainStates per query query_cache_key = ( schema , query_signature ) start_time = time . time ( ) self . _log . debug ( 'explaining statement. schema=%s, statement="%s"' , schema , obfuscated_statement ) error_state = self . _use_schema ( cursor , schema , explain_state_cache_key ) if error_state : self . _log . debug ( 'Failed to collect execution plan because schema could not be accessed. schema=%s error=%s: %s' , schema , error_state , obfuscated_statement , ) return None , [ error_state ] preferred_strategies = list ( self . _preferred_explain_strategies ) explain_state = self . _collection_strategy_cache . get ( explain_state_cache_key , EMPTY_EXPLAIN_STATE ) # if we've cached a successful non-error strategy, put it at the front of the list if explain_state . strategy and not explain_state . error_code : preferred_strategies . remove ( explain_state . strategy ) preferred_strategies . insert ( 0 , explain_state . strategy ) # if there is a default schema on the connection then the only way we can guarantee collection of plans is # by having that same default schema set when invoking the EXPLAIN, which is what we do with the "PROCEDURE" # strategy. If there is no default schema then we can collect it from anywhere, typically FQ_PROCEDURE, which # is expected to be in the dedicated datadog schema. optimal_strategy = "PROCEDURE" if schema else "FQ_PROCEDURE" is_optimal_strategy_cached = explain_state . strategy == optimal_strategy and not explain_state . error_code if is_optimal_strategy_cached : # if the optimal strategy was cached then that means at least one recent query must have been successfully # collected from this schema and therefore the schema is probably setup OK. # knowing that the schema is setup OK, if we fail to collect an execution plan for a given query then # it's likely a problem with that specific query and it's not worth trying it again so we cache those # errors for a longer time to avoid excessive futile explain attempts. cached_error_states = self . _explain_error_states_cache . get ( query_cache_key , None ) if cached_error_states : self . _log . debug ( 'Skipping execution plan collection for query due to cached failures %s: %s' , cached_error_states , obfuscated_statement , ) return None , cached_error_states error_states = [ ] for strategy in preferred_strategies : if not schema and strategy == "PROCEDURE" : self . _log . debug ( 'skipping PROCEDURE strategy as there is no default schema for this statement="%s"' , obfuscated_statement , ) continue try : plan = self . _explain_strategies [ strategy ] ( cursor , statement , obfuscated_statement ) if plan : self . _collection_strategy_cache [ explain_state_cache_key ] = ExplainState ( strategy = strategy , error_code = None , error_message = None ) self . _log . debug ( 'Successfully collected execution plan. strategy=%s, schema=%s, statement="%s"' , strategy , schema , obfuscated_statement , ) self . _check . histogram ( "dd.mysql.run_explain.time" , ( time . time ( ) - start_time ) * 1000 , tags = self . _tags + [ "strategy:{}" . format ( strategy ) ] , hostname = self . _check . resolved_hostname , ) return plan , None except pymysql . err . DatabaseError as e : if len ( e . args ) != 2 : raise error_state = ExplainState ( strategy = strategy , error_code = DBExplainErrorCode . database_error , error_message = str ( type ( e ) ) ) error_states . append ( error_state ) self . _log . debug ( 'Failed to collect execution plan. error=%s, strategy=%s, schema=%s, statement="%s"' , e . args , strategy , schema , obfuscated_statement , ) continue if is_optimal_strategy_cached and error_states : self . _log . debug ( 'Caching execution plan failure for query to skip future explains %s: %s' , error_states , obfuscated_statement , ) self . _explain_error_states_cache [ query_cache_key ] = error_states return None , error_states