mysql-server/sql/iterators/timing_iterator.h
2025-03-05 14:31:37 +07:00

234 lines
8.1 KiB
C++

#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 <inttypes.h>
#include <stdio.h>
#include <chrono>
#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<double>(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 RealIterator>
class TimingIterator final : public RowIterator {
public:
template <class... Args>
TimingIterator(THD *thd, Args &&...args)
: RowIterator(thd), m_iterator(thd, std::forward<Args>(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<T>,
// so that it collects timing information. For this reason, nearly all
// instantiations of iterators should go through this function.
template <class RealIterator, class... Args>
unique_ptr_destroy_only<RowIterator> NewIterator(THD *thd, MEM_ROOT *mem_root,
Args &&...args) {
if (thd->lex->is_explain_analyze) {
return unique_ptr_destroy_only<RowIterator>(
new (mem_root)
TimingIterator<RealIterator>(thd, std::forward<Args>(args)...));
} else {
return unique_ptr_destroy_only<RowIterator>(
new (mem_root) RealIterator(thd, std::forward<Args>(args)...));
}
}
#endif // SQL_ITERATORS_TIMING_ITERATOR_H_