如何将 performance_schema 中的 TIMER 字段转换为日期时间
问题
最近有好几个朋友问,如何将 performance_schema.events_statements_xxx 中的 TIMER 字段(主要是TIMER_START和TIMER_END)转换为日期时间。
因为 TIMER 字段的单位是皮秒(picosecond),所以很多童鞋会尝试直接转换,但转换后的结果并不对,看下面这个示例。
mysql> select * from performance_schema.events_statements_current limit 1\G
*************************** 1. row ***************************
THREAD_ID: 57
EVENT_ID: 13
END_EVENT_ID: 13
EVENT_NAME: statement/sql/commit
SOURCE: log_event.cc:4825
TIMER_START: 3304047000000
TIMER_END: 3305287000000
TIMER_WAIT: 1240000000
...
EXECUTION_ENGINE: PRIMARY
1 row in set (0.00 sec)
# 因为1秒等于10^12皮秒,所以需要先除以 1000000000000。
mysql> select from_unixtime(3304047000000/1000000000000);
+--------------------------------------------+
| from_unixtime(3304047000000/1000000000000) |
+--------------------------------------------+
| 1970-01-01 08:00:03.3040 |
+--------------------------------------------+
1 row in set (0.00 sec)
下面会从源码角度分析 TIMER 字段的生成逻辑。
对源码分析不感兴趣的童鞋,可直接跳到后面的案例部分看结论。
TIMER 字段的生成逻辑
当我们查询 events_statements_xxx 表时,会调用对应的 make_row() 函数来生成行数据。
如 events_statements_current 表,对应的生成函数是 table_events_statements_current::make_row()。
make_row 会调用 make_row_part_1 和 make_row_part_2 来生成数据。
TIMER_START、TIMER_END 实际上就是table_events_statements_common::make_row_part_1调用to_pico来生成的。
int table_events_statements_common::make_row_part_1(
PFS_events_statements *statement, sql_digest_storage *digest) {
ulonglong timer_end;
...
m_normalizer->to_pico(statement->m_timer_start, timer_end,
&m_row.m_timer_start, &m_row.m_timer_end,
&m_row.m_timer_wait);
m_row.m_lock_time = statement->m_lock_time * MICROSEC_TO_PICOSEC;
m_row.m_name = klass->m_name.str();
m_row.m_name_length = klass->m_name.length();
...
return 0;
}
void time_normalizer::to_pico(ulonglong start, ulonglong end,
ulonglong *pico_start, ulonglong *pico_end,
ulonglong *pico_wait) {
if (start == 0) {
*pico_start = 0;
*pico_end = 0;
*pico_wait = 0;
} else {
*pico_start = (start - m_v0) * m_factor;
if (end == 0) {
*pico_end = 0;
*pico_wait = 0;
} else {
*pico_end = (end - m_v0) * m_factor;
*pico_wait = (end - start) * m_factor;
}
}
}
函数中的 start 和 end 分别对应语句的开始时间(m_timer_start)和结束时间(m_timer_end)。
如果 start,end 不为 0,则 pico_start = (start - m_v0) * m_factor,pico_end = (end - m_v0) * m_factor。
pico_start、pico_end 即我们在 events_statements_current 中看到的 TIMER_START 和 TIMER_END。
m_timer_start 和 m_timer_end 的实现逻辑
如果 performance_schema.setup_instruments 中 statement 相关的采集项开启了(默认开启),则语句在开始和结束时会分别调用pfs_start_statement_vc() 和pfs_end_statement_vc()这两个函数。
m_timer_start 和 m_timer_end 实际上就是在这两个函数中被赋值的。
void pfs_start_statement_vc(PSI_statement_locker *locker, const char *db,
uint db_len, const char *src_file, uint src_line) {
...
if (flags & STATE_FLAG_TIMED) {
timer_start = get_statement_timer();
state->m_timer_start = timer_start;
}
...
pfs->m_timer_start = timer_start;
...
}
void pfs_end_statement_vc(PSI_statement_locker *locker, void *stmt_da) {
...
if (flags & STATE_FLAG_TIMED) {
timer_end = get_statement_timer();
wait_time = timer_end - state->m_timer_start;
}
...
pfs->m_timer_end = timer_end;
...
}
可以看到,无论是语句开始时间(timer_start)还是结束时间(timer_end),调用的都是get_statement_timer()。
接下来,我们看看get_statement_timer()的具体实现。
ulonglong inline get_statement_timer() { return USED_TIMER(); }
# 如果有其它的计数器实现,只需更新宏定义即可。
#define USED_TIMER my_timer_nanoseconds
ulonglong my_timer_nanoseconds(void) {
...
#elif defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_REALTIME)
{
struct timespec tp;
clock_gettime(CLOCK_REALTIME, &tp);
return (ulonglong)tp.tv_sec * 1000000000 + (ulonglong)tp.tv_nsec;
}
...
#else
return 0;
#endif
}
get_statement_timer()调用的是 USED_TIMER(),而 USED_TIMER 只不过是个宏定义,实际调用的还是my_timer_nanoseconds。
my_timer_nanoseconds是一个计时器函数,用于获取系统当前时间,并将其转换为纳秒级别的时间戳。不同的系统,会使用不同的方法来获取。
对于 linux 系统,它会首先调用clock_gettime函数获取系统当前时间,然后再将其转换为纳秒。
所以,语句的开始时间(m_timer_start)和结束时间(m_timer_end)取的都是系统当前时间。
m_v0 和 m_factor 的实现逻辑
m_v0和m_factor是结构体 time_normalizer 中的两个变量。其中,
- m_v0:实例的启动时间(计数器值)。
- m_factor:将计数器值转换为皮秒的转换因子。
这两个变量是在实例启动时被赋值的。
void init_timers(void) {
double pico_frequency = 1.0e12;
...
my_timer_init(&pfs_timer_info);
...
cycle_v0 = my_timer_cycles();
nanosec_v0 = my_timer_nanoseconds(); # 获取系统当前时间,以纳秒表示。
...
if (pfs_timer_info.nanoseconds.frequency > 0) {
nanosec_to_pico =
lrint(pico_frequency / (double)pfs_timer_info.nanoseconds.frequency);
} else {
nanosec_to_pico = 0;
}
...
to_pico_data[TIMER_NAME_NANOSEC].m_v0 = nanosec_v0;
to_pico_data[TIMER_NAME_NANOSEC].m_factor = nanosec_to_pico;
...
}
可以看到,nanosec_v0 调用的函数,实际上同 m_timer_start、m_timer_end 一样,都是my_timer_nanoseconds。
nanosec_to_pico 是将纳秒转换为皮秒的转换因子,等于 1.0e12/1.0e9 = 1000。
案例
基于上面的分析,我们总结下 TIMER_START 的计算公式。
TIMER_START = (语句执行时的系统时间(单位纳秒)- 实例启动时的系统时间(单位纳秒))* 1000
所以,如果要获取语句执行时的系统时间,可将 TIMER_START 除以 1000,然后再加上实例启动时的系统时间。
而实例启动时的系统时间,可通过当前时间(now)减去Uptime这个状态变量来实现。
下面我们通过一个具体的案例来验证下。
mysql> create database test;
Query OK, 1 row affected (0.01 sec)
mysql> create table test.t1(id int primary key, c1 datetime(6));
Query OK, 0 rows affected (0.05 sec)
mysql> insert into test.t1 values(1, now(6));
Query OK, 1 row affected (0.02 sec)
mysql> select * from test.t1;
+----+----------------------------+
| id | c1 |
+----+----------------------------+
| 1 | 2023-12-05 23:57:01.892242 |
+----+----------------------------+
1 row in set (0.01 sec)
mysql> select * from performance_schema.events_statements_history where digest_text like '%insert%'\G
*************************** 1. row ***************************
THREAD_ID: 69
EVENT_ID: 8
END_EVENT_ID: 9
EVENT_NAME: statement/sql/insert
SOURCE: init_net_server_extension.cc:97
TIMER_START: 24182166000000
TIMER_END: 24208896000000
TIMER_WAIT: 26730000000
LOCK_TIME: 254000000
SQL_TEXT: insert into test.t1 values(1, now(6))
DIGEST: b2e0770f7505d35d2894321783fe92b7ebfbb908f687b98966efdc58d3386b3c
DIGEST_TEXT: INSERT INTO `test` . `t1` VALUES ( ? , NOW (?) )
...
EXECUTION_ENGINE: PRIMARY
1 row in set (0.04 sec)
mysql> select (unix_timestamp(now(6)) - variable_value) * 1000000000 into @mysql_start_time from performance_schema.global_status where variable_name = 'uptime';
Query OK, 1 row affected (0.02 sec)
mysql> select sql_text, timer_start, from_unixtime((timer_start/1000 + @mysql_start_time)/1000000000) as formatted_time from performance_schema.events_statements_history where digest_text like '%insert%';
+---------------------------------------+----------------+----------------------------+
| sql_text | timer_start | formatted_time |
+---------------------------------------+----------------+----------------------------+
| insert into test.t1 values(1, now(6)) | 24182166000000 | 2023-12-05 23:57:02.356767 |
+---------------------------------------+----------------+----------------------------+
1 row in set (0.01 sec)
插入时间(2023-12-05 23:57:01.892242)和 formatted_time(2023-12-05 23:57:02.356767)基本吻合,相差不到 0.5s。
为什么会有误差呢?
Uptime这个状态变量的单位是秒。- 语句的开始时间(m_timer_start)要比语句中的 now(6) 这个时间早。
细节补充
为了可读性,上面其实忽略了很多细节,这里简单记录下。
1. to_pico_data
to_pico_data是个数组,这个数组包含了多个 time_normalizer 类型的元素。
实例启动,在调用init_timers函数时,实际上还会将以微秒、毫秒为单位的系统时间分别赋值给to_pico_data[TIMER_NAME_MICROSEC].m_v0、to_pico_data[TIMER_NAME_MILLISEC].m_v0。
to_pico_data[TIMER_NAME_CYCLE].m_v0 = cycle_v0;
to_pico_data[TIMER_NAME_CYCLE].m_factor = cycle_to_pico;
to_pico_data[TIMER_NAME_NANOSEC].m_v0 = nanosec_v0;
to_pico_data[TIMER_NAME_NANOSEC].m_factor = nanosec_to_pico;
to_pico_data[TIMER_NAME_MICROSEC].m_v0 = microsec_v0;
to_pico_data[TIMER_NAME_MICROSEC].m_factor = microsec_to_pico;
to_pico_data[TIMER_NAME_MILLISEC].m_v0 = millisec_v0;
to_pico_data[TIMER_NAME_MILLISEC].m_factor = millisec_to_pico;
既然有这么多个 m_v0,怎么知道time_normalizer::to_pico函数取的是哪一个呢?
实际上,events_statements_xxx 系列表的实现中,有个基类table_events_statements_common。
该类的构造函数里面会基于time_normalizer::get_statement()来初始化 m_normalizer,
而time_normalizer::get_statement()实际上返回的就是to_pico_data[TIMER_NAME_NANOSEC]。
table_events_statements_common::table_events_statements_common(
const PFS_engine_table_share *share, void *pos)
: PFS_engine_table(share, pos) {
m_normalizer = time_normalizer::get_statement();
}
time_normalizer *time_normalizer::get_statement() {
return &to_pico_data[USED_TIMER_NAME];
}
#define USED_TIMER_NAME TIMER_NAME_NANOSEC
2. performance_schema 表的实现注释
storage/perfschema/pfs.cc文件中有一段注释。
这段注释非常重要,它介绍了 performance_schema 中的表是如何实现的。
以下是 events_statements_xxx 相关的注释。
...
Implemented as:
- [1] #pfs_start_statement_vc(), #pfs_end_statement_vc()
(1a, 1b) is an aggregation by EVENT_NAME,
(1c, 1d, 1e) is an aggregation by TIME,
(1f) is an aggregation by DIGEST
all of these are orthogonal,
and implemented in #pfs_end_statement_vc().
- [2] #pfs_delete_thread_v1(), #aggregate_thread_statements()
- [3] @c PFS_account::aggregate_statements()
- [4] @c PFS_host::aggregate_statements()
- [A] EVENTS_STATEMENTS_SUMMARY_BY_THREAD_BY_EVENT_NAME,
@c table_esms_by_thread_by_event_name::make_row()
...
- [H] EVENTS_STATEMENTS_HISTORY_LONG,
@c table_events_statements_history_long::make_row()
- [I] EVENTS_STATEMENTS_SUMMARY_BY_DIGEST
@c table_esms_by_digest::make_row()
3. 如何知道 TIMER 字段对应 m_row 中的哪些变量?
两者的对应关系实际上是在table_events_statements_common::read_row_values中定义的。
int table_events_statements_common::read_row_values(TABLE *table,
unsigned char *buf,
Field **fields,
bool read_all) {
Field *f;
uint len;
/* Set the null bits */
assert(table->s->null_bytes == 3);
buf[0] = 0;
buf[1] = 0;
buf[2] = 0;
for (; (f = *fields); fields++) {
if (read_all || bitmap_is_set(table->read_set, f->field_index())) {
switch (f->field_index()) {
case 0: /* THREAD_ID */
set_field_ulonglong(f, m_row.m_thread_internal_id);
break;
...
case 5: /* TIMER_START */
if (m_row.m_timer_start != 0) {
set_field_ulonglong(f, m_row.m_timer_start);
} else {
f->set_null();
}
break;
case 6: /* TIMER_END */
if (m_row.m_timer_end != 0) {
set_field_ulonglong(f, m_row.m_timer_end);
} else {
f->set_null();
}
break;
...
如何将 performance_schema 中的 TIMER 字段转换为日期时间的更多相关文章
- Angularjs在控制器(controller.js)的js代码中使用过滤器($filter)格式化日期/时间实例
Angularjs内置的过滤器(filter)为我们的数据信息格式化提供了比较强大的功能,比如:格式化时间,日期.格式化数字精度.语言本地化.格式化货币等等.但这些过滤器一般都是在VIEW中使用的,比 ...
- 【SQL】MySQL内置函数中的字符串函数和日期时间函数
字符串函数 --拼接字符串组成新的字符串 Select concat(‘A’,’B’); --返回字符串长度 Select length(“CAT”) --返回子字符串在字符串中首次出现的位置,没有返 ...
- excel中如何将时间戳转换为日期格式
https://www.cnblogs.com/xueluozhangxin/p/5868225.html =TEXT((B2/1000+8*3600)/86400+70*365+19,"y ...
- jquery输出ajax返回数据中的时间戳转化为日期时间的函数
//第一种 function getLocalTime(nS) { ).toLocaleString().replace(/:\d{,}$/,' '); } alert(getLocalTime()) ...
- jdk8中LocalDateTime,LocalDate,LocalTime等日期时间类
package com.zy.time; import org.junit.Test; import java.time.*; import java.time.format.DateTimeForm ...
- 【JavaScript】JavaScript中的Timer是怎么工作的( setTimeout,setInterval)
原文(http://www.yeeyan.org/articles/view/luosheng/24380) 作为入门者来说,了解JavaScript中timer的工作方式是很重要的.通常它们的表现行 ...
- [转]JDBC中日期时间的处理技巧
Java中用类java.util.Date对日期/时间做了封装,此类提供了对年.月.日.时.分.秒.毫秒以及时区的控制方法,同时也提供一些工具方法,比如日期/时间的比较,前后判断等. java.uti ...
- 【Java 与数据库】JDBC中日期时间的处理技巧
JDBC中日期时间的处理技巧 详谈Java.util.Date和Java.sql.Date 基础知识 Java中用类java.util.Date对日期/时间做了封装,此类提供了对年.月.日.时.分.秒 ...
- DB2中字符、数字和日期类型之间的转换
DB2中字符.数字和日期类型之间的转换 一般我们在使用DB2或Oracle的过程中,经常会在数字<->字符<->日期三种类 型之间做转换,那么在DB2和Oracle中,他们分别 ...
- SQL中的日期时间函数
之所以把日期时间函数单独拿出来回顾一下,是因为这一部分的内容比较独立,C#中也有类似的日期时间函数,趁着想得起来,写个标题先.
随机推荐
- TCP的可靠性之道:确认重传和流量控制
TCP 全称为 Transmission Control Protocol(传输控制协议),是一种面向连接的.可靠的.基于字节流的传输层通信协议,其中可靠性是相对于其他传输协议的优势点.TCP 为了确 ...
- KVM下windows由IDE模式改为virtio模式蓝屏 开不开机
KVM安装Windows默认使用的是qemu虚拟化IDE硬盘模式,在这种情况下,IO性能比较低,如果使用virtio的方式可以提高虚拟机IO性能. 于是我想将这台虚拟机迁移到openstack中管理 ...
- Programming abstractions in C阅读笔记:p132-p137
<Programming Abstractions In C>学习第53天,p132-p137,3.2小节"strings"总结如下: 一.技术总结 3.2小节介绍了字 ...
- 如何调用API接口获取淘宝商品数据
淘宝商品数据的获取是一项非常重要的技术,它可以为淘宝卖家和买家提供有利的数据分析和扩展市场的机会.调用API接口是一种快速.方便.高效的方式获取淘宝商品数据. 以下是一些步骤来调用API接口来获取淘宝 ...
- 原来你是这样的JAVA[06]-反射
1.JVM为每个加载的class及interface创建了对应的Class实例来保存class及interface的所有信息: 获取一个class对应的Class实例后,就可以获取该class的所有信 ...
- docker bridge网络类型研究
bridge模式是docker的默认网络模式,使用docker run -p时,docker实际是在iptables做了DNAT规则,实现端口转发功能.可以使用iptables -t nat -vnL ...
- MySQL系列之MHA高可用——主从复制架构演变介绍、高可用MHA、管理员在高可用架构维护的职责
文章目录 1. 主从复制架构演变介绍 1.1 基本结构 1.2 高级应用架构演变 1.2.1 高性能架构 1.2.2 高可用架构 2. 高可用MHA ***** 2.1 架构工作原理 2.2 架构介绍 ...
- PyCharm配置autopep8(自动格式化Python代码)
PyCharm配置autopep8(自动格式化Python代码) 1. 关于PEP 8 PEP 8,Style Guide for Python Code,是Python官方推出编码约定,主要是为 ...
- Robert Kiraly Software Developer
Robert KiralySoftware DeveloperCell Phone: 650-600-2520 Freenode: ##venturesSupports text messages P ...
- django admin字段设置大全
# 在列表页显示的字段,默认会显示所有字段,有对应的方法可以重写 list_display = ('__str__',) # 在列表页显示的字段中,可以链接到change_form页面的字段 list ...