#ifndef SQL_ITERATORS_TIMING_ITERATOR_H_ #define SQL_ITERATORS_TIMING_ITERATOR_H_ /* Copyright (c) 2019, 2024, Oracle and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by the Free Software Foundation. This program is designed to work with certain software (including but not limited to OpenSSL) that is licensed under separate terms, as designated in a particular file or component or in included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the separately licensed software that they have either included with the program or referenced in the documentation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0, for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include "my_alloc.h" #include "sql/iterators/row_iterator.h" #include "sql/sql_class.h" #include "sql/sql_lex.h" /** This class is used in implementing the 'EXPLAIN ANALYZE' command. It maintains a set of profiling data. */ class IteratorProfilerImpl final : public IteratorProfiler { public: // To avoid a lot of repetitive writing. using steady_clock = std::chrono::steady_clock; using duration = steady_clock::time_point::duration; using TimeStamp = steady_clock::time_point; /** Return current time.*/ static TimeStamp Now() { #if defined(__linux__) // Work around very slow libstdc++ implementations of std::chrono // (those compiled with _GLIBCXX_USE_CLOCK_GETTIME_SYSCALL). timespec tp; clock_gettime(CLOCK_MONOTONIC, &tp); return steady_clock::time_point( steady_clock::duration(std::chrono::seconds(tp.tv_sec) + std::chrono::nanoseconds(tp.tv_nsec))); #else return steady_clock::now(); #endif } double GetFirstRowMs() const override { return DurationToMs(m_elapsed_first_row); } double GetLastRowMs() const override { return DurationToMs(m_elapsed_first_row + m_elapsed_other_rows); } uint64_t GetNumInitCalls() const override { return m_num_init_calls; } uint64_t GetNumRows() const override { return m_num_rows; } /** Mark the end of an iterator->Init() call.*/ void StopInit(TimeStamp start_time) { m_elapsed_first_row += Now() - start_time; m_num_init_calls++; m_first_row = true; } /** Update the number of rows read. Note that this function is only called for iterator where we read all rows during iterator->Init() (@see MaterializeIterator and @see TemptableAggregateIterator). */ void IncrementNumRows(uint64_t materialized_rows) { m_num_rows += materialized_rows; } /** Mark the end of an iterator->Read() call. @param start_time time when Read() started. @param read_ok 'true' if Read() was successful. */ void StopRead(TimeStamp start_time, bool read_ok) { if (m_first_row) { m_elapsed_first_row += Now() - start_time; m_first_row = false; } else { m_elapsed_other_rows += Now() - start_time; } if (read_ok) { m_num_rows++; } } private: static double DurationToMs(duration dur) { return std::chrono::duration(dur).count() * 1e3; } /** The number of loops.*/ uint64_t m_num_init_calls{0}; /** The number of rows fetched. (Sum for all loops.)*/ uint64_t m_num_rows{0}; /** True if we are about to read the first row.*/ bool m_first_row; /** Elapsed time in all calls to m_iterator.Init() and Read() for the first row. */ duration m_elapsed_first_row{0}; /** Elapsed time in all calls to m_iterator.Read() for all but the first row. */ duration m_elapsed_other_rows{0}; }; /** An iterator template that wraps a RowIterator, such that all calls to Init() and Read() are timed (all others are passed through unchanged, and possibly even inlined, since all RowIterator implementations are final). This is used for EXPLAIN ANALYZE. Note that MaterializeIterator does not use this class. Doing so would give misleading measurements. MaterializeIterator has an internal member iterator (m_table_iterator) that iterates over the materialized result. Calls to Init()/Read() on that iterator goes via Init()/Read() on the MaterializeIterator. And the internal iterator is listed above MaterializeIterator in 'EXPLAIN ANALYZE' output. Its elapsed time values should thus include both the cost of materialization and iterating over the result, while the entry for MaterializeIterator should only show the time spent on materialization. But if we used TimingIterator, the entry for MaterializeIterator would give the sum of time spent on both materialization and iteration, and the entry for the internal iterator would only show the time spent on iterating over the materialized result. (See also Bug #33834146 "'EXPLAIN ANALYZE' cost estimates and elapsed time values are not cumulative"). This also applies to TemptableAggregateIterator. These classes therefore have other mechanisms for obtaining profiling data. See also NewIterator, below. */ template class TimingIterator final : public RowIterator { public: template TimingIterator(THD *thd, Args &&... args) : RowIterator(thd), m_iterator(thd, std::forward(args)...) {} bool Init() override { const IteratorProfilerImpl::TimeStamp start_time = IteratorProfilerImpl::Now(); bool err = m_iterator.Init(); m_profiler.StopInit(start_time); return err; } int Read() override { const IteratorProfilerImpl::TimeStamp start_time = IteratorProfilerImpl::Now(); int err = m_iterator.Read(); m_profiler.StopRead(start_time, err == 0); return err; } void SetNullRowFlag(bool is_null_row) override { m_iterator.SetNullRowFlag(is_null_row); } void UnlockRow() override { m_iterator.UnlockRow(); } void StartPSIBatchMode() override { m_iterator.StartPSIBatchMode(); } void EndPSIBatchModeIfStarted() override { m_iterator.EndPSIBatchModeIfStarted(); } void SetOverrideProfiler(const IteratorProfiler *profiler) override { m_override_profiler = profiler; } const IteratorProfiler *GetProfiler() const override { return m_override_profiler == nullptr ? &m_profiler : m_override_profiler; } RealIterator *real_iterator() override { return &m_iterator; } const RealIterator *real_iterator() const override { return &m_iterator; } private: /** This maintains the profiling measurements.*/ IteratorProfilerImpl m_profiler; /** For iterators over materialized tables we must make profiling measurements in a different way. This field keeps those measurements. */ const IteratorProfiler *m_override_profiler{nullptr}; RealIterator m_iterator; }; // Allocates a new iterator on the given MEM_ROOT. The MEM_ROOT must live // for at least as long as the iterator does. // // If we are in EXPLAIN ANALYZE, the iterator is wrapped in a TimingIterator, // so that it collects timing information. For this reason, nearly all // instantiations of iterators should go through this function. template unique_ptr_destroy_only NewIterator(THD *thd, MEM_ROOT *mem_root, Args &&... args) { if (thd->lex->is_explain_analyze) { return unique_ptr_destroy_only( new (mem_root) TimingIterator(thd, std::forward(args)...)); } else { return unique_ptr_destroy_only( new (mem_root) RealIterator(thd, std::forward(args)...)); } } #endif // SQL_ITERATORS_TIMING_ITERATOR_H_