#!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0 r""" Export perf data to a postgresql database. This script has been ported to use the modern perf Python module and libpq via ctypes. It no longer requires PySide2 or QtSql for exporting. The script assumes postgresql is running on the local machine and that the user has postgresql permissions to create databases. An example of using this script with Intel PT: $ perf record -e intel_pt//u ls $ python export-to-postgresql.py -i perf.data -o pt_example To browse the database, psql can be used e.g. $ psql pt_example pt_example=# select * from samples_view where id < 100; pt_example=# \d+ pt_example=# \d+ samples_view pt_example=# \q An example of using the database is provided by the script exported-sql-viewer.py. Refer to that script for details. Tables: The tables largely correspond to perf tools' data structures. They are largely self-explanatory. samples 'samples' is the main table. It represents what instruction was executing at a point in time when something (a selected event) happened. The memory address is the instruction pointer or 'ip'. branch_types 'branch_types' provides descriptions for each type of branch. comm_threads 'comm_threads' shows how 'comms' relates to 'threads'. comms 'comms' contains a record for each 'comm' - the name given to the executable that is running. dsos 'dsos' contains a record for each executable file or library. machines 'machines' can be used to distinguish virtual machines if virtualization is supported. selected_events 'selected_events' contains a record for each kind of event that has been sampled. symbols 'symbols' contains a record for each symbol. Only symbols that have samples are present. threads 'threads' contains a record for each thread. Views: Most of the tables have views for more friendly display. The views are: comm_threads_view dsos_view machines_view samples_view symbols_view threads_view Ported from tools/perf/scripts/python/export-to-postgresql.py """ from __future__ import annotations import typing import argparse from ctypes import CDLL, c_char_p, c_int, c_void_p, c_ubyte import ctypes.util import os import shutil import struct import tempfile import sys from typing import Any, Dict, Optional import perf # Need to access PostgreSQL C library directly to use COPY FROM STDIN libpq_name = ctypes.util.find_library("pq") if not libpq_name: libpq_name = "libpq.so.5" try: libpq = CDLL(libpq_name) except OSError as e: print(f"Error loading {libpq_name}: {e}") print("Please ensure PostgreSQL client library is installed.") sys.exit(1) PQconnectdb = libpq.PQconnectdb PQconnectdb.restype = c_void_p PQconnectdb.argtypes = [c_char_p] PQfinish = libpq.PQfinish PQfinish.argtypes = [c_void_p] PQstatus = libpq.PQstatus PQstatus.restype = c_int PQstatus.argtypes = [c_void_p] libpq.PQerrorMessage.restype = c_char_p libpq.PQerrorMessage.argtypes = [c_void_p] PQexec = libpq.PQexec PQexec.restype = c_void_p PQexec.argtypes = [c_void_p, c_char_p] PQresultStatus = libpq.PQresultStatus PQresultStatus.restype = c_int PQresultStatus.argtypes = [c_void_p] PQputCopyData = libpq.PQputCopyData PQputCopyData.restype = c_int PQputCopyData.argtypes = [c_void_p, c_void_p, c_int] PQputCopyEnd = libpq.PQputCopyEnd PQputCopyEnd.restype = c_int PQputCopyEnd.argtypes = [c_void_p, c_void_p] PQgetResult = libpq.PQgetResult PQgetResult.restype = c_void_p PQgetResult.argtypes = [c_void_p] PQclear = libpq.PQclear PQclear.argtypes = [c_void_p] def toserverstr(s: str) -> bytes: """Convert string to server encoding (UTF-8).""" return bytes(s, "UTF_8") def toclientstr(s: str) -> bytes: """Convert string to client encoding (UTF-8).""" return bytes(s, "UTF_8") PERF_IP_FLAG_BRANCH = 1 << 0 PERF_IP_FLAG_CALL = 1 << 1 PERF_IP_FLAG_RETURN = 1 << 2 PERF_IP_FLAG_CONDITIONAL = 1 << 3 PERF_IP_FLAG_SYSCALLRET = 1 << 4 PERF_IP_FLAG_ASYNC = 1 << 5 PERF_IP_FLAG_INTERRUPT = 1 << 6 PERF_IP_FLAG_TX_ABORT = 1 << 7 PERF_IP_FLAG_TRACE_BEGIN = 1 << 8 PERF_IP_FLAG_TRACE_END = 1 << 9 PERF_IP_FLAG_IN_TX = 1 << 10 PERF_IP_FLAG_VMENTRY = 1 << 11 PERF_IP_FLAG_VMEXIT = 1 << 12 BRANCH_TYPES = [ (0, "no branch"), (PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_CALL, "call"), (PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_RETURN, "return"), (PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_CONDITIONAL, "conditional jump"), (PERF_IP_FLAG_BRANCH, "unconditional jump"), (PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_CALL | PERF_IP_FLAG_INTERRUPT, "software interrupt"), (PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_RETURN | PERF_IP_FLAG_INTERRUPT, "return from interrupt"), (PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_CALL | PERF_IP_FLAG_SYSCALLRET, "system call"), (PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_RETURN | PERF_IP_FLAG_SYSCALLRET, "return from system call"), (PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_ASYNC, "asynchronous branch"), (PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_CALL | PERF_IP_FLAG_ASYNC | PERF_IP_FLAG_INTERRUPT, "hardware interrupt"), (PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_TX_ABORT, "transaction abort"), (PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_TRACE_BEGIN, "trace begin"), (PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_TRACE_END, "trace end"), (PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_CALL | PERF_IP_FLAG_VMENTRY, "vm entry"), (PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_CALL | PERF_IP_FLAG_VMEXIT, "vm exit"), ] for _b_type, _b_name in list(BRANCH_TYPES): if (_b_type == PERF_IP_FLAG_BRANCH or (_b_type & (PERF_IP_FLAG_TRACE_BEGIN | PERF_IP_FLAG_TRACE_END))): continue BRANCH_TYPES.append((_b_type | PERF_IP_FLAG_TRACE_BEGIN, f"trace begin / {_b_name}")) BRANCH_TYPES.append((_b_type | PERF_IP_FLAG_TRACE_END, f"{_b_name} / trace end")) class PostgresExporter: """Handles PostgreSQL connection and exporting of perf events.""" def __init__(self, dbname: str): self.dbname = dbname self.conn = None self.session: Optional[perf.session] = None self.output_dir_name = tempfile.mkdtemp(prefix=dbname + "-perf-data-") self.created_output_dir = True self.file_header = struct.pack("!11sii", b"PGCOPY\n\377\r\n\0", 0, 0) self.file_trailer = b"\377\377" # Caches and counters grouped to reduce instance attributes self.caches: Dict[str, dict] = { 'machines': {}, 'threads': {}, 'comms': {}, 'dsos': {}, 'symbols': {}, 'events': {}, 'branch_types': {}, 'call_paths': {} } self.symbol_rows: Dict[int, tuple[int, int, int, int, str]] = {} self.next_id = { 'machine': 1, 'thread': 1, 'comm': 1, 'dso': 1, 'symbol': 1, 'event': 1, 'branch_type': 1, 'comm_thread': 1, 'call_path': 1, 'sample': 1, 'call': 1, 'context_switch': 1 } self.files: Dict[str, Any] = {} self.unhandled_count = 0 def connect(self, db_to_use: str) -> None: """Connect to database. Reject URI schemes ('://') and key=value pairs ('=') to prevent connection parameter injection, and escape backslashes and single quotes per libpq single-quoted conninfo string rules. """ if "://" in db_to_use or "=" in db_to_use: raise ValueError(f"Invalid database name: {db_to_use}") safe_db = db_to_use.replace('\\', '\\\\').replace("'", "\\'") conn_str = toclientstr(f"dbname='{safe_db}'") self.conn = PQconnectdb(conn_str) if PQstatus(self.conn) != 0: PQfinish(self.conn) self.conn = None raise RuntimeError(f"PQconnectdb failed for {db_to_use}") def disconnect(self) -> None: """Disconnect from database.""" if self.conn: PQfinish(self.conn) self.conn = None def do_query(self, sql: str) -> None: """Execute a query and check status.""" res = PQexec(self.conn, toserverstr(sql)) status = PQresultStatus(res) PQclear(res) if status not in (1, 2): # PGRES_COMMAND_OK, PGRES_TUPLES_OK error_msg = libpq.PQerrorMessage(self.conn).decode('utf-8') raise RuntimeError(f"Query failed: {sql}. Error: {error_msg}") def open_output_file(self, file_name: str): """Open intermediate binary file.""" path_name = self.output_dir_name + "/" + file_name f = open(path_name, "wb+") f.write(self.file_header) return f def close_output_file(self, f): """Close intermediate binary file.""" f.write(self.file_trailer) f.close() def copy_output_file(self, path_name: str, table_name: str): """Copy intermediate file to database.""" sql = f"COPY {table_name} FROM STDIN (FORMAT 'binary')" res = PQexec(self.conn, toserverstr(sql)) if PQresultStatus(res) != 4: # PGRES_COPY_IN PQclear(res) err = libpq.PQerrorMessage(self.conn).decode() raise RuntimeError(f"COPY FROM STDIN PQexec failed for {table_name}: {err}") PQclear(res) with open(path_name, "rb") as f: data = f.read(65536) while len(data) > 0: c_data = (c_ubyte * len(data)).from_buffer_copy(data) ret = PQputCopyData(self.conn, c_data, len(data)) if ret != 1: raise RuntimeError(f"PQputCopyData failed for {table_name}") data = f.read(65536) ret = PQputCopyEnd(self.conn, None) if ret != 1: err = libpq.PQerrorMessage(self.conn).decode() raise RuntimeError(f"PQputCopyEnd failed for {table_name}: {err}") res = PQgetResult(self.conn) while res: status = PQresultStatus(res) if status != 1: # PGRES_COMMAND_OK error_msg = libpq.PQerrorMessage(self.conn).decode('utf-8') PQclear(res) raise RuntimeError( f"COPY completion failed for {table_name}. Status {status}: {error_msg}" ) PQclear(res) res = PQgetResult(self.conn) def setup_db(self) -> None: """Create database and tables. MUST be called after init.""" if "://" in self.dbname or "=" in self.dbname: raise ValueError(f"Invalid database name: {self.dbname}") self.created_output_dir = True self.connect('postgres') try: # SQL identifiers are double-quoted; escape embedded double quotes by doubling them. db_name = self.dbname.replace('"', '""') self.do_query(f'CREATE DATABASE "{db_name}"') except Exception as db_error: shutil.rmtree(self.output_dir_name, ignore_errors=True) raise db_error self.disconnect() self.connect(self.dbname) self.do_query("SET client_min_messages TO WARNING") self.do_query(""" CREATE TABLE selected_events ( id bigint NOT NULL, name varchar(80)) """) self.do_query(""" CREATE TABLE machines ( id bigint NOT NULL, pid integer, root_dir varchar(4096)) """) self.do_query(""" CREATE TABLE threads ( id bigint NOT NULL, machine_id bigint, process_id bigint, pid integer, tid integer) """) self.do_query(""" CREATE TABLE comms ( id bigint NOT NULL, comm varchar(16), c_thread_id bigint, c_time bigint, exec_flag boolean) """) self.do_query(""" CREATE TABLE comm_threads ( id bigint NOT NULL, comm_id bigint, thread_id bigint) """) self.do_query(""" CREATE TABLE dsos ( id bigint NOT NULL, machine_id bigint, short_name varchar(256), long_name varchar(4096), build_id varchar(64)) """) self.do_query(""" CREATE TABLE symbols ( id bigint NOT NULL, dso_id bigint, sym_start bigint, sym_end bigint, binding integer, name varchar(2048)) """) self.do_query(""" CREATE TABLE branch_types ( id integer NOT NULL, name varchar(80)) """) self.do_query(""" CREATE TABLE samples ( id bigint NOT NULL, evsel_id bigint, machine_id bigint, thread_id bigint, comm_id bigint, dso_id bigint, symbol_id bigint, sym_offset bigint, ip bigint, time bigint, cpu integer, to_dso_id bigint, to_symbol_id bigint, to_sym_offset bigint, to_ip bigint, period bigint, weight bigint, transaction_ bigint, data_src bigint, branch_type integer, in_tx boolean, call_path_id bigint, insn_count bigint, cyc_count bigint, flags integer) """) self.do_query(''' CREATE TABLE context_switches ( id bigint, machine_id bigint, time bigint, cpu integer, thread_out_id bigint, comm_out_id bigint, thread_in_id bigint, comm_in_id bigint, flags integer ) ''') self.do_query(''' CREATE TABLE call_paths ( id bigint NOT NULL, parent_id bigint, symbol_id bigint, ip bigint) ''') self.do_query(''' CREATE TABLE calls ( id bigint NOT NULL, thread_id bigint, comm_id bigint, call_path_id bigint, call_time bigint, return_time bigint, branch_count bigint, call_id bigint, return_id bigint, parent_call_path_id bigint, flags integer, parent_id bigint, insn_count bigint, cyc_count bigint ) ''') self.do_query(''' CREATE TABLE ptwrite ( id bigint NOT NULL, payload bigint, exact_ip boolean ) ''') self.do_query(''' CREATE TABLE cbr ( id bigint NOT NULL, cbr integer, mhz integer, percent integer ) ''') self.do_query(''' CREATE TABLE mwait ( id bigint NOT NULL, hints integer, extensions integer ) ''') self.do_query(''' CREATE TABLE pwre ( id bigint NOT NULL, cstate integer, subcstate integer, hw boolean ) ''') self.do_query(''' CREATE TABLE exstop ( id bigint NOT NULL, exact_ip boolean ) ''') self.do_query(''' CREATE TABLE pwrx ( id bigint NOT NULL, deepest_cstate integer, last_cstate integer, wake_reason integer ) ''') self.files['evsel'] = self.open_output_file("evsel_table.bin") self.files['machine'] = self.open_output_file("machine_table.bin") self.files['thread'] = self.open_output_file("thread_table.bin") self.files['comm'] = self.open_output_file("comm_table.bin") self.files['comm_thread'] = self.open_output_file("comm_thread_table.bin") self.files['dso'] = self.open_output_file("dso_table.bin") self.files['symbol'] = self.open_output_file("symbol_table.bin") self.files['branch_type'] = self.open_output_file("branch_type_table.bin") self.files['sample'] = self.open_output_file("sample_table.bin") self.files['context_switches'] = self.open_output_file("context_switches_table.bin") self.files['call'] = self.open_output_file("call_table.bin") self.files['call_path'] = self.open_output_file("call_path_table.bin") self.files['ptwrite'] = self.open_output_file("ptwrite_table.bin") self.files['cbr'] = self.open_output_file("cbr_table.bin") self.files['mwait'] = self.open_output_file("mwait_table.bin") self.files['pwre'] = self.open_output_file("pwre_table.bin") self.files['exstop'] = self.open_output_file("exstop_table.bin") self.files['pwrx'] = self.open_output_file("pwrx_table.bin") self.write_evsel(0, "unknown") self.write_machine(0, 0, "unknown") self.write_thread(0, 0, 0, -1, -1) self.write_comm(0, "unknown", 0, 0, 0) self.write_dso(0, 0, "unknown", "unknown", "") self.write_symbol(0, 0, 0, 0, 0, "unknown") self.write_call_path(0, 0, 0, 0) # Sentinel id = 0 rows in sample_table.bin and call_table.bin satisfy # PostgreSQL FOREIGN KEY constraints (call_id/return_id -> samples.id # and parent references) when a call or return event has no matching # sample reference (0). self.files['sample'].write(struct.pack( "!hiqiqiqiqiqiqiqiQiQiQiIiqiqiQiQiQiQiQiQiiiBiqiQiQii", 25, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 4, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 4, 0, 1, 0, 8, 0, 8, 0, 8, 0, 4, 0 )) self.files['call'].write(struct.pack( "!hiqiqiqiqiqiqiqiqiqiqiiiqiqiq", 14, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 4, 0, 8, 0, 8, 0, 8, 0 )) for b_type, b_name in BRANCH_TYPES: self.write_branch_type(b_type, b_name) def write_branch_type(self, branch_type: int, name: str) -> None: name_bytes = toserverstr(name) n = len(name_bytes) fmt = "!hiii" + str(n) + "s" value = struct.pack(fmt, 2, 4, branch_type, n, name_bytes) self.files['branch_type'].write(value) def write_ptwrite(self, id_: int, raw_buf: bytes) -> None: data = struct.unpack_from(" None: data = struct.unpack_from(" None: data = struct.unpack_from("> 32) & 0x3 value = struct.pack("!hiqiiii", 3, 8, id_, 4, hints, 4, extensions) self.files['mwait'].write(value) def write_pwre(self, id_: int, raw_buf: bytes) -> None: data = struct.unpack_from("> 7) & 1 cstate = (payload >> 12) & 0xf subcstate = (payload >> 8) & 0xf value = struct.pack("!hiqiiiiiB", 4, 8, id_, 4, cstate, 4, subcstate, 1, hw) self.files['pwre'].write(value) def write_exstop(self, id_: int, raw_buf: bytes) -> None: data = struct.unpack_from(" None: data = struct.unpack_from("> 4) & 0xf wake = (payload >> 8) & 0xf value = struct.pack("!hiqiiiiii", 4, 8, id_, 4, deepest, 4, last, 4, wake) self.files['pwrx'].write(value) def write_evsel(self, evsel_id: int, name: str) -> None: """Write event to binary file.""" name_bytes = toserverstr(name) n = len(name_bytes) fmt = "!hiqi" + str(n) + "s" value = struct.pack(fmt, 2, 8, evsel_id, n, name_bytes) self.files['evsel'].write(value) def write_machine(self, machine_id: int, pid: int, root_dir: str) -> None: """Write machine to binary file.""" rd_bytes = toserverstr(root_dir) n = len(rd_bytes) fmt = "!hiqiii" + str(n) + "s" value = struct.pack(fmt, 3, 8, machine_id, 4, pid, n, rd_bytes) self.files['machine'].write(value) def write_thread(self, thread_id: int, machine_id: int, process_id: int, pid: int, tid: int) -> None: """Write thread to binary file.""" value = struct.pack("!hiqiqiqiIiI", 5, 8, thread_id, 8, machine_id, 8, process_id, 4, pid & 0xffffffff, 4, tid & 0xffffffff) self.files['thread'].write(value) def write_comm(self, comm_id: int, comm_str: str, thread_id: int, time: int, exec_flag: int) -> None: """Write comm to binary file.""" comm_bytes = toserverstr(comm_str) n = len(comm_bytes) fmt = "!hiqi" + str(n) + "s" + "iqiqiB" value = struct.pack(fmt, 5, 8, comm_id, n, comm_bytes, 8, thread_id, 8, time, 1, exec_flag) self.files['comm'].write(value) def write_comm_thread(self, comm_thread_id: int, comm_id: int, thread_id: int) -> None: """Write comm_thread to binary file.""" fmt = "!hiqiqiq" value = struct.pack(fmt, 3, 8, comm_thread_id, 8, comm_id, 8, thread_id) self.files['comm_thread'].write(value) def write_dso(self, dso_id: int, machine_id: int, short_name: str, long_name: str, build_id: str) -> None: """Write DSO to binary file.""" sn_bytes = toserverstr(short_name) ln_bytes = toserverstr(long_name) bi_bytes = toserverstr(build_id) n1, n2, n3 = len(sn_bytes), len(ln_bytes), len(bi_bytes) fmt = "!hiqiqi" + str(n1) + "si" + str(n2) + "si" + str(n3) + "s" value = struct.pack(fmt, 5, 8, dso_id, 8, machine_id, n1, sn_bytes, n2, ln_bytes, n3, bi_bytes) self.files['dso'].write(value) def write_symbol(self, symbol_id: int, dso_id: int, sym_start: int, sym_end: int, binding: int, symbol_name: str) -> None: """Write symbol to binary file.""" name_bytes = toserverstr(symbol_name) n = len(name_bytes) fmt = "!hiqiqiQiQiii" + str(n) + "s" value = struct.pack(fmt, 6, 8, symbol_id, 8, dso_id, 8, sym_start, 8, sym_end, 4, binding, n, name_bytes) self.files['symbol'].write(value) def write_call_path(self, cp_id: int, parent_id: int, symbol_id: int, ip: int) -> None: """Write call path to binary file.""" fmt = "!hiqiqiqiQ" value = struct.pack(fmt, 4, 8, cp_id, 8, parent_id, 8, symbol_id, 8, ip) self.files['call_path'].write(value) def write_sample(self, sample_id: int, evsel_id: int, machine_id: int, thread_id: int, comm_id: int, dso_id: int, symbol_id: int, sample: perf.sample_event, call_path_id: int) -> None: """Write sample to binary file.""" addr_dso_id = self.get_dso_id( sample.addr_dso or "Unknown_dso", sample.addr_dso or "Unknown_dso_long", "", machine_id ) addr_symbol_id = self.get_symbol_id( addr_dso_id, sample.addr_symbol or "Unknown_symbol", 0, 0 ) value = struct.pack( "!hiqiqiqiqiqiqiqiQiQiQiIiqiqiQiQiQiQiQiQiiiBiqiQiQii", 25, 8, sample_id, 8, evsel_id, 8, machine_id, 8, thread_id, 8, comm_id, 8, dso_id, 8, symbol_id, 8, getattr(sample, 'sym_offset', 0) or 0, 8, getattr(sample, 'sample_ip', 0) or 0, 8, getattr(sample, 'sample_time', 0) or 0, 4, getattr(sample, 'sample_cpu', 0) & 0xffffffff, 8, addr_dso_id, 8, addr_symbol_id, 8, getattr(sample, 'addr_sym_offset', 0) or 0, 8, getattr(sample, 'sample_addr', 0) or 0, 8, getattr(sample, 'sample_period', 0) or 0, 8, getattr(sample, 'sample_weight', 0) or 0, 8, getattr(sample, 'transaction', 0) or 0, 8, getattr(sample, 'sample_data_src', 0) or 0 or 0, 4, getattr(sample, 'branch_type', 0) or 0, 1, getattr(sample, 'in_tx', 0) or 0, 8, call_path_id, 8, getattr(sample, 'sample_insn_count', 0) or 0 or 0, 8, getattr(sample, 'sample_cyc_count', 0) or 0 or 0, 4, getattr(sample, 'flags', 0) or 0 ) self.files['sample'].write(value) def get_machine_id(self, machine_pid: Optional[int]) -> int: """Get or create machine ID.""" if machine_pid is None or machine_pid <= 0 or machine_pid > 0x7fffffff: machine_pid = -1 if machine_pid in self.caches['machines']: return self.caches['machines'][machine_pid] machine_id = self.next_id['machine'] self.next_id['machine'] += 1 self.write_machine(machine_id, machine_pid, "") self.caches['machines'][machine_pid] = machine_id return machine_id def get_event_id(self, name: str) -> int: """Get or create event ID.""" if name in self.caches['events']: return self.caches['events'][name] event_id = self.next_id['event'] self.write_evsel(event_id, name) self.caches['events'][name] = event_id self.next_id['event'] += 1 return event_id def get_thread_id(self, machine_id: int, pid: Optional[int], tid: Optional[int]) -> int: """Get or create thread ID.""" if pid is None: pid = -1 elif pid > 0x7fffffff: pid -= 0x100000000 if tid is None: tid = -1 elif tid > 0x7fffffff: tid -= 0x100000000 key = (machine_id, pid, tid) if key in self.caches['threads']: return self.caches['threads'][key] process_id = self.get_thread_id(machine_id, pid, pid) if tid != pid else -1 thread_id = self.next_id['thread'] if process_id == -1: process_id = thread_id self.write_thread(thread_id, machine_id, process_id, pid, tid) self.caches['threads'][key] = thread_id self.next_id['thread'] += 1 return thread_id def get_comm_id(self, comm: str, thread_id: int) -> int: """Get or create comm ID.""" c_key = (comm, thread_id) if c_key in self.caches['comms']: comm_id = self.caches['comms'][c_key] else: comm_id = self.next_id['comm'] self.write_comm(comm_id, comm, thread_id, 0, 0) self.caches['comms'][c_key] = comm_id self.next_id['comm'] += 1 key = (comm_id, thread_id) if 'comm_threads' not in self.caches: self.caches['comm_threads'] = {} if key not in self.caches['comm_threads']: comm_thread_id = self.next_id['comm_thread'] self.write_comm_thread(comm_thread_id, comm_id, thread_id) self.caches['comm_threads'][key] = True self.next_id['comm_thread'] += 1 return comm_id def get_dso_id(self, short_name: str, long_name: str, build_id: str, machine_id: int = 0) -> int: """Get or create DSO ID.""" key = (machine_id, short_name, long_name, build_id) if key in self.caches['dsos']: return self.caches['dsos'][key] short_key = (machine_id, short_name) if not build_id and short_key in self.caches['dsos']: return self.caches['dsos'][short_key] dso_id = self.next_id['dso'] self.write_dso(dso_id, machine_id, short_name, long_name, build_id) self.caches['dsos'][key] = dso_id self.caches['dsos'][short_key] = dso_id self.next_id['dso'] += 1 return dso_id def get_symbol_id(self, dso_id: int, name: str, start: int, end: int) -> int: """Get or create symbol ID.""" key = (dso_id, name) if key in self.caches['symbols']: symbol_id, cached_start, cached_end = self.caches['symbols'][key] if cached_start == 0 and cached_end == 0 and (start != 0 or end != 0): self.caches['symbols'][key] = (symbol_id, start, end) self.symbol_rows[symbol_id] = (dso_id, start, end, 0, name) return symbol_id symbol_id = self.next_id['symbol'] self.caches['symbols'][key] = (symbol_id, start, end) self.symbol_rows[symbol_id] = (dso_id, start, end, 0, name) self.next_id['symbol'] += 1 return symbol_id def get_call_path_id(self, parent_id: int, symbol_id: int, ip: int) -> int: """Get or create call path ID.""" key = (parent_id, symbol_id, ip) if key in self.caches['call_paths']: return self.caches['call_paths'][key] call_path_id = self.next_id['call_path'] self.write_call_path(call_path_id, parent_id, symbol_id, ip) self.caches['call_paths'][key] = call_path_id self.next_id['call_path'] += 1 return call_path_id def process_context_switch(self, event: typing.Any) -> None: """Callback for processing context switch events.""" misc = getattr(event, 'misc', 0) out = bool(misc & (1 << 13)) out_preempt = bool(misc & (1 << 14)) flags = (1 if out else 0) | ((1 if out_preempt else 0) << 1) machine_id = self.get_machine_id(getattr(event, 'machine_pid', None)) sample_pid = getattr(event, 'sample_pid', None) if sample_pid is None: sample_pid = -1 elif sample_pid > 0x7fffffff: sample_pid -= 0x100000000 sample_tid = getattr(event, 'sample_tid', None) if sample_tid is None: sample_tid = -1 elif sample_tid > 0x7fffffff: sample_tid -= 0x100000000 next_prev_pid = getattr(event, 'next_prev_pid', None) if next_prev_pid is None: next_prev_pid = -1 elif next_prev_pid > 0x7fffffff: next_prev_pid -= 0x100000000 next_prev_tid = getattr(event, 'next_prev_tid', None) if next_prev_tid is None: next_prev_tid = -1 elif next_prev_tid > 0x7fffffff: next_prev_tid -= 0x100000000 th_a_id = self.get_thread_id(machine_id, sample_pid, sample_tid) comm_a = "Unknown_comm" if self.session: try: proc = self.session.find_thread(sample_pid, sample_tid) if proc: comm_a_name = proc.comm() or "Unknown_comm" if comm_a_name: comm_a = comm_a_name except TypeError: pass comm_a_id = self.get_comm_id(comm_a, th_a_id) th_b_id = self.get_thread_id(machine_id, next_prev_pid, next_prev_tid) comm_b = "Unknown_comm" if self.session: try: proc = self.session.find_thread(next_prev_pid, next_prev_tid) if proc: comm_b_name = proc.comm() or "Unknown_comm" if comm_b_name: comm_b = comm_b_name except TypeError: pass comm_b_id = self.get_comm_id(comm_b, th_b_id) if out: th_out_id = th_a_id comm_out_id = comm_a_id th_in_id = th_b_id comm_in_id = comm_b_id else: th_out_id = th_b_id comm_out_id = comm_b_id th_in_id = th_a_id comm_in_id = comm_a_id cs_id = self.next_id['context_switch'] self.next_id['context_switch'] += 1 time = getattr(event, "time", getattr(event, "sample_time", 0)) cpu = getattr(event, "sample_cpu", 0) or 0 if cpu > 0x7fffffff: cpu -= 0x100000000 # 9 columns total: fmt = "!hiQiqiQiiiqiqiqiqii" value = struct.pack( fmt, 9, 8, cs_id, 8, machine_id, 8, time, 4, cpu, 8, th_out_id, 8, comm_out_id, 8, th_in_id, 8, comm_in_id, 4, flags ) self.files["context_switches"].write(value) def get_call_path_ids(self, call_path: typing.Any, machine_id: int = 0) -> tuple[int, int]: """Add a perf.callchain as call_path rows, return its id and parent's.""" parent_id = 0 call_path_id = 0 for node in call_path: dso_name = node.dso or "Unknown_dso" symbol_name = node.symbol or "Unknown_symbol" dso_id = self.get_dso_id(dso_name, dso_name, "", machine_id) symbol_id = self.get_symbol_id(dso_id, symbol_name, 0, 0) parent_id = call_path_id call_path_id = self.get_call_path_id(parent_id, symbol_id, node.ip) return call_path_id, parent_id def process_call_return(self, cr: perf.call_return) -> None: """Callback for processing call_return events.""" machine_id = self.get_machine_id(getattr(cr, "machine_pid", 0) or 0) thread_id = self.get_thread_id(machine_id, cr.pid if cr.pid is not None else -1, cr.tid if cr.tid is not None else -1) comm_id = self.get_comm_id(cr.comm or "Unknown_comm", thread_id) call_path_id, parent_call_path_id = self.get_call_path_ids(cr.call_path or [], machine_id) fmt = "!hiqiqiqiqiqiqiqiqiqiqiiiqiqiq" # call.id and call.parent_id use the db_id of the perf module, as a call # is given an id before it returns its parent cannot be numbered here. value = struct.pack( fmt, 14, 8, cr.db_id, 8, thread_id, 8, comm_id, 8, call_path_id, 8, cr.call_time, 8, cr.return_time, 8, cr.branch_count, 8, cr.call_ref, 8, cr.return_ref, 8, parent_call_path_id, 4, cr.flags, 8, cr.parent_id, 8, cr.insn_count, 8, cr.cyc_count ) self.files['call'].write(value) def process_event(self, sample: typing.Any) -> None: """Callback for processing events.""" machine_db_id = getattr(sample, 'machine_pid', 0) machine_id = self.get_machine_id(machine_db_id) thread_id = self.get_thread_id(machine_id, sample.sample_pid, sample.sample_tid) comm = "Unknown_comm" try: if self.session is not None: proc = self.session.find_thread(sample.sample_pid, sample.sample_tid) if proc: comm = proc.comm() or "Unknown_comm" except TypeError: pass comm_id = self.get_comm_id(comm, thread_id) dso_id = self.get_dso_id( sample.dso or "Unknown_dso", sample.dso_long_name or "Unknown_dso_long", sample.dso_bid or "", machine_id ) symbol_id = self.get_symbol_id( dso_id, sample.symbol or "Unknown_symbol", sample.sym_start or 0, sample.sym_end or 0 ) call_path_id = 0 if hasattr(sample, 'callchain') and sample.callchain: parent_id = 0 for node in reversed(sample.callchain): node_dso = getattr(node, 'dso', None) or getattr(node, 'map', None) node_symbol = getattr(node, 'symbol', None) or getattr(node, 'sym', None) dso_name = "Unknown_dso" if node_dso: dso_name = (node_dso if isinstance(node_dso, str) else getattr(node_dso, 'name', "Unknown_dso") or "Unknown_dso") symbol_name = "Unknown_symbol" if node_symbol: symbol_name = (node_symbol if isinstance(node_symbol, str) else getattr(node_symbol, 'name', "Unknown_symbol") or "Unknown_symbol") node_dso_id = self.get_dso_id(dso_name, dso_name, "", machine_id) node_symbol_id = self.get_symbol_id(node_dso_id, symbol_name, 0, 0) parent_id = self.get_call_path_id(parent_id, node_symbol_id, node.ip) call_path_id = parent_id sample_id = self.next_id['sample'] evsel_name = str(sample.evsel) if evsel_name.startswith("evsel(") and evsel_name.endswith(")"): evsel_name = evsel_name[6:-1] self.write_sample(sample_id, self.get_event_id(evsel_name), machine_id, thread_id, comm_id, dso_id, symbol_id, sample, call_path_id) event_name_str = evsel_name if hasattr(sample, "raw_buf"): if event_name_str == "ptwrite": self.write_ptwrite(sample_id, sample.raw_buf) elif event_name_str == "cbr": self.write_cbr(sample_id, sample.raw_buf) elif event_name_str == "mwait": self.write_mwait(sample_id, sample.raw_buf) elif event_name_str == "pwre": self.write_pwre(sample_id, sample.raw_buf) elif event_name_str == "exstop": self.write_exstop(sample_id, sample.raw_buf) elif event_name_str == "pwrx": self.write_pwrx(sample_id, sample.raw_buf) self.next_id['sample'] += 1 def finalize(self) -> None: """Copy files to database and add keys/views.""" print("Copying to database...") for symbol_id, (dso_id, sym_start, sym_end, binding, name) in self.symbol_rows.items(): self.write_symbol(symbol_id, dso_id, sym_start, sym_end, binding, name) for name, f in self.files.items(): self.close_output_file(f) table_mapping = { 'evsel': 'selected_events', 'machine': 'machines', 'thread': 'threads', 'comm': 'comms', 'comm_thread': 'comm_threads', 'dso': 'dsos', 'symbol': 'symbols', 'branch_type': 'branch_types', 'sample': 'samples', 'call': 'calls', 'call_path': 'call_paths', 'context_switches': 'context_switches', 'ptwrite': 'ptwrite', 'cbr': 'cbr', 'mwait': 'mwait', 'pwre': 'pwre', 'exstop': 'exstop', 'pwrx': 'pwrx' } for name, f in self.files.items(): table_name = table_mapping.get(name, name + "s") self.copy_output_file(f.name, table_name) print("Removing intermediate files...") for name, f in self.files.items(): os.unlink(f.name) shutil.rmtree(self.output_dir_name, ignore_errors=True) print("Adding primary keys") self.do_query("ALTER TABLE selected_events ADD PRIMARY KEY (id)") self.do_query("ALTER TABLE machines ADD PRIMARY KEY (id)") self.do_query("ALTER TABLE threads ADD PRIMARY KEY (id)") self.do_query("ALTER TABLE comms ADD PRIMARY KEY (id)") self.do_query("ALTER TABLE comm_threads ADD PRIMARY KEY (id)") self.do_query("ALTER TABLE dsos ADD PRIMARY KEY (id)") self.do_query("ALTER TABLE symbols ADD PRIMARY KEY (id)") self.do_query("ALTER TABLE branch_types ADD PRIMARY KEY (id)") self.do_query("ALTER TABLE samples ADD PRIMARY KEY (id)") self.do_query("ALTER TABLE call_paths ADD PRIMARY KEY (id)") self.do_query("ALTER TABLE calls ADD PRIMARY KEY (id)") self.do_query("ALTER TABLE ptwrite ADD PRIMARY KEY (id)") self.do_query("ALTER TABLE cbr ADD PRIMARY KEY (id)") self.do_query("ALTER TABLE mwait ADD PRIMARY KEY (id)") self.do_query("ALTER TABLE pwre ADD PRIMARY KEY (id)") self.do_query("ALTER TABLE exstop ADD PRIMARY KEY (id)") self.do_query("ALTER TABLE pwrx ADD PRIMARY KEY (id)") self.do_query("ALTER TABLE context_switches ADD PRIMARY KEY (id)") print("Adding foreign keys") self.do_query('ALTER TABLE threads ' 'ADD CONSTRAINT machinefk FOREIGN KEY (machine_id) REFERENCES machines (id),' 'ADD CONSTRAINT processfk FOREIGN KEY (process_id) REFERENCES threads (id)') self.do_query('ALTER TABLE comms ' 'ADD CONSTRAINT threadfk FOREIGN KEY (c_thread_id) REFERENCES threads (id)') self.do_query('ALTER TABLE comm_threads ' 'ADD CONSTRAINT commfk FOREIGN KEY (comm_id) REFERENCES comms (id),' 'ADD CONSTRAINT threadfk FOREIGN KEY (thread_id) REFERENCES threads (id)') self.do_query('ALTER TABLE dsos ' 'ADD CONSTRAINT machinefk FOREIGN KEY (machine_id) REFERENCES machines (id)') self.do_query('ALTER TABLE symbols ' 'ADD CONSTRAINT dsofk FOREIGN KEY (dso_id) REFERENCES dsos (id)') self.do_query('ALTER TABLE samples ' 'ADD CONSTRAINT evselfk FOREIGN KEY (evsel_id) ' 'REFERENCES selected_events (id),' 'ADD CONSTRAINT machinefk FOREIGN KEY (machine_id) REFERENCES machines (id),' 'ADD CONSTRAINT threadfk FOREIGN KEY (thread_id) REFERENCES threads (id),' 'ADD CONSTRAINT commfk FOREIGN KEY (comm_id) REFERENCES comms (id),' 'ADD CONSTRAINT dsofk FOREIGN KEY (dso_id) REFERENCES dsos (id),' 'ADD CONSTRAINT symbolfk FOREIGN KEY (symbol_id) REFERENCES symbols (id),' 'ADD CONSTRAINT todsofk FOREIGN KEY (to_dso_id) REFERENCES dsos (id),' 'ADD CONSTRAINT tosymbolfk FOREIGN KEY (to_symbol_id) ' 'REFERENCES symbols (id)') self.do_query('ALTER TABLE call_paths ' 'ADD CONSTRAINT parentfk FOREIGN KEY (parent_id) REFERENCES call_paths (id),' 'ADD CONSTRAINT symbolfk FOREIGN KEY (symbol_id) REFERENCES symbols (id)') self.do_query('ALTER TABLE calls ' 'ADD CONSTRAINT threadfk FOREIGN KEY (thread_id) REFERENCES threads (id),' 'ADD CONSTRAINT commfk FOREIGN KEY (comm_id) REFERENCES comms (id),' 'ADD CONSTRAINT call_pathfk FOREIGN KEY (call_path_id) ' 'REFERENCES call_paths (id),' 'ADD CONSTRAINT callfk FOREIGN KEY (call_id) REFERENCES samples (id),' 'ADD CONSTRAINT returnfk FOREIGN KEY (return_id) REFERENCES samples (id),' 'ADD CONSTRAINT parent_call_pathfk FOREIGN KEY (parent_call_path_id) ' 'REFERENCES call_paths (id)') # pcpid_idx, pid_idx, and comms.has_calls are required by # exported-sql-viewer.py to filter the command dropdown to threads with # call graphs and to expand parent/child call trees efficiently. self.do_query('CREATE INDEX pcpid_idx ON calls (parent_call_path_id)') self.do_query('CREATE INDEX pid_idx ON calls (parent_id)') self.do_query('ALTER TABLE comms ADD has_calls boolean') self.do_query('UPDATE comms SET has_calls = TRUE WHERE comms.id IN ' '(SELECT DISTINCT comm_id FROM calls)') self.do_query('ALTER TABLE context_switches ' 'ADD CONSTRAINT machinefk FOREIGN KEY (machine_id) REFERENCES machines (id),' 'ADD CONSTRAINT toutfk FOREIGN KEY (thread_out_id) REFERENCES threads (id),' 'ADD CONSTRAINT tinfk FOREIGN KEY (thread_in_id) REFERENCES threads (id),' 'ADD CONSTRAINT coutfk FOREIGN KEY (comm_out_id) REFERENCES comms (id),' 'ADD CONSTRAINT cinfk FOREIGN KEY (comm_in_id) REFERENCES comms (id)') self.do_query('ALTER TABLE ptwrite ' 'ADD CONSTRAINT idfk FOREIGN KEY (id) REFERENCES samples (id)') self.do_query('ALTER TABLE cbr ' 'ADD CONSTRAINT idfk FOREIGN KEY (id) REFERENCES samples (id)') self.do_query('ALTER TABLE mwait ' 'ADD CONSTRAINT idfk FOREIGN KEY (id) REFERENCES samples (id)') self.do_query('ALTER TABLE pwre ' 'ADD CONSTRAINT idfk FOREIGN KEY (id) REFERENCES samples (id)') self.do_query('ALTER TABLE exstop ' 'ADD CONSTRAINT idfk FOREIGN KEY (id) REFERENCES samples (id)') self.do_query('ALTER TABLE pwrx ' 'ADD CONSTRAINT idfk FOREIGN KEY (id) REFERENCES samples (id)') print("Creating views...") self.do_query( "CREATE VIEW machines_view AS " "SELECT id, pid, root_dir, " "CASE WHEN id=0 THEN 'unknown' WHEN pid=-1 THEN 'host' ELSE 'guest' END " "AS host_or_guest FROM machines" ) self.do_query( "CREATE VIEW dsos_view AS " "SELECT id, machine_id, " "(SELECT host_or_guest FROM machines_view WHERE id = machine_id) AS host_or_guest, " "short_name, long_name, build_id FROM dsos" ) self.do_query( "CREATE VIEW symbols_view AS " "SELECT id, name, (SELECT short_name FROM dsos WHERE id=dso_id) AS dso, " "dso_id, sym_start, sym_end, " "CASE WHEN binding=0 THEN 'local' WHEN binding=1 THEN 'global' ELSE 'weak' END " "AS binding FROM symbols" ) self.do_query( "CREATE VIEW threads_view AS " "SELECT id, machine_id, " "(SELECT host_or_guest FROM machines_view WHERE id = machine_id) AS host_or_guest, " "process_id, pid, tid FROM threads" ) self.do_query( "CREATE VIEW comm_threads_view AS " "SELECT comm_id, (SELECT comm FROM comms WHERE id = comm_id) AS command, " "thread_id, (SELECT pid FROM threads WHERE id = thread_id) AS pid, " "(SELECT tid FROM threads WHERE id = thread_id) AS tid FROM comm_threads" ) self.do_query( "CREATE VIEW call_paths_view AS " "SELECT c.id, to_hex(c.ip) AS ip, c.symbol_id, " "(SELECT name FROM symbols WHERE id = c.symbol_id) AS symbol, " "(SELECT dso_id FROM symbols WHERE id = c.symbol_id) AS dso_id, " "(SELECT dso FROM symbols_view WHERE id = c.symbol_id) AS dso_short_name, " "c.parent_id, to_hex(p.ip) AS parent_ip, p.symbol_id AS parent_symbol_id, " "(SELECT name FROM symbols WHERE id = p.symbol_id) AS parent_symbol, " "(SELECT dso_id FROM symbols WHERE id = p.symbol_id) AS parent_dso_id, " "(SELECT dso FROM symbols_view WHERE id = p.symbol_id) AS parent_dso_short_name " "FROM call_paths c LEFT JOIN call_paths p ON p.id = c.parent_id" ) self.do_query( "CREATE VIEW calls_view AS " "SELECT calls.id, thread_id, " "(SELECT pid FROM threads WHERE id = thread_id) AS pid, " "(SELECT tid FROM threads WHERE id = thread_id) AS tid, " "(SELECT comm FROM comms WHERE id = comm_id) AS command, " "call_path_id, to_hex(ip) AS ip, symbol_id, " "(SELECT name FROM symbols WHERE id = symbol_id) AS symbol, " "call_time, return_time, return_time - call_time AS elapsed_time, " "branch_count, insn_count, cyc_count, " "CASE WHEN cyc_count=0 THEN CAST(0 AS FLOAT) " "ELSE CAST(insn_count AS FLOAT) / cyc_count END AS IPC, " "call_id, return_id, " "CASE WHEN flags=0 THEN '' WHEN flags=1 THEN 'no call' " "WHEN flags=2 THEN 'no return' WHEN flags=3 THEN 'no call/return' " "WHEN flags IN (4, 6) THEN 'jmp' ELSE CAST(flags AS VARCHAR(6)) END AS flags, " "parent_call_path_id, calls.parent_id " "FROM calls INNER JOIN call_paths ON call_paths.id = call_path_id" ) self.do_query( "CREATE VIEW samples_view AS " "SELECT id, time, cpu, " "(SELECT pid FROM threads WHERE id = thread_id) AS pid, " "(SELECT tid FROM threads WHERE id = thread_id) AS tid, " "(SELECT comm FROM comms WHERE id = comm_id) AS command, " "(SELECT name FROM selected_events WHERE id = evsel_id) AS event, " "to_hex(ip) AS ip_hex, " "(SELECT name FROM symbols WHERE id = symbol_id) AS symbol, sym_offset, " "(SELECT short_name FROM dsos WHERE id = dso_id) AS dso_short_name, " "to_hex(to_ip) AS to_ip_hex, " "(SELECT name FROM symbols WHERE id = to_symbol_id) AS to_symbol, to_sym_offset, " "(SELECT short_name FROM dsos WHERE id = to_dso_id) AS to_dso_short_name, " "(SELECT name FROM branch_types WHERE id = branch_type) AS branch_type_name, " "in_tx, call_path_id, insn_count, cyc_count, " "CASE WHEN cyc_count=0 THEN CAST(0 AS FLOAT) " "ELSE CAST(insn_count AS FLOAT) / cyc_count END AS IPC, flags FROM samples" ) self.do_query( "CREATE VIEW context_switches_view AS " "SELECT context_switches.id, context_switches.machine_id, " "context_switches.time, context_switches.cpu, " "th_out.pid AS pid_out, th_out.tid AS tid_out, comm_out.comm AS comm_out, " "th_in.pid AS pid_in, th_in.tid AS tid_in, comm_in.comm AS comm_in, " "CASE WHEN flags=0 THEN 'in' WHEN flags=1 THEN 'out' " "WHEN flags=3 THEN 'out preempt' ELSE CAST(flags AS VARCHAR(6)) END AS flags " "FROM context_switches " "INNER JOIN threads AS th_out ON th_out.id = context_switches.thread_out_id " "INNER JOIN threads AS th_in ON th_in.id = context_switches.thread_in_id " "INNER JOIN comms AS comm_out ON comm_out.id = context_switches.comm_out_id " "INNER JOIN comms AS comm_in ON comm_in.id = context_switches.comm_in_id" ) self.do_query( "CREATE VIEW ptwrite_view AS " "SELECT ptwrite.id, time, cpu, to_hex(payload) AS payload_hex, " "CASE WHEN exact_ip=FALSE THEN 'False' ELSE 'True' END AS exact_ip " "FROM ptwrite INNER JOIN samples ON samples.id = ptwrite.id" ) self.do_query( "CREATE VIEW cbr_view AS " "SELECT cbr.id, time, cpu, cbr, mhz, percent " "FROM cbr INNER JOIN samples ON samples.id = cbr.id" ) self.do_query( "CREATE VIEW mwait_view AS " "SELECT mwait.id, time, cpu, to_hex(hints) AS hints_hex, " "to_hex(extensions) AS extensions_hex " "FROM mwait INNER JOIN samples ON samples.id = mwait.id" ) self.do_query( "CREATE VIEW pwre_view AS " "SELECT pwre.id, time, cpu, cstate, subcstate, " "CASE WHEN hw=FALSE THEN 'False' ELSE 'True' END AS hw " "FROM pwre INNER JOIN samples ON samples.id = pwre.id" ) self.do_query( "CREATE VIEW exstop_view AS " "SELECT exstop.id, time, cpu, " "CASE WHEN exact_ip=FALSE THEN 'False' ELSE 'True' END AS exact_ip " "FROM exstop INNER JOIN samples ON samples.id = exstop.id" ) self.do_query( "CREATE VIEW pwrx_view AS " "SELECT pwrx.id, time, cpu, deepest_cstate, last_cstate, " "CASE WHEN wake_reason=1 THEN 'Interrupt' " "WHEN wake_reason=2 THEN 'Timer Deadline' " "WHEN wake_reason=4 THEN 'Monitored Address' " "WHEN wake_reason=8 THEN 'HW' WHEN wake_reason=16 THEN 'Other' " "ELSE CAST ( wake_reason AS VARCHAR(2) ) END AS wake_reason " "FROM pwrx INNER JOIN samples ON samples.id = pwrx.id" ) self.do_query( "CREATE VIEW power_events_view AS " "SELECT samples.id, samples.time, samples.cpu, selected_events.name AS event, " "FORMAT('%6s', cbr.cbr) AS cbr, FORMAT('%6s', cbr.mhz) AS MHz, " "FORMAT('%5s', cbr.percent) AS percent, to_hex(mwait.hints) AS hints_hex, " "to_hex(mwait.extensions) AS extensions_hex, FORMAT('%3s', pwre.cstate) AS cstate, " "FORMAT('%3s', pwre.subcstate) AS subcstate, " "CASE WHEN pwre.hw=FALSE THEN 'False' WHEN pwre.hw=TRUE THEN 'True' " "ELSE NULL END AS hw, " "CASE WHEN exstop.exact_ip=FALSE THEN 'False' WHEN exstop.exact_ip=TRUE THEN 'True' " "ELSE NULL END AS exact_ip, FORMAT('%3s', pwrx.deepest_cstate) AS deepest_cstate, " "FORMAT('%3s', pwrx.last_cstate) AS last_cstate, " "CASE WHEN pwrx.wake_reason=1 THEN 'Interrupt' " "WHEN wake_reason=2 THEN 'Timer Deadline' " "WHEN wake_reason=4 THEN 'Monitored Address' " "WHEN wake_reason=8 THEN 'HW' WHEN wake_reason=16 THEN 'Other' " "ELSE FORMAT('%2s', pwrx.wake_reason) END AS wake_reason " "FROM cbr FULL JOIN mwait ON mwait.id = cbr.id " "FULL JOIN pwre ON pwre.id = cbr.id FULL JOIN exstop ON exstop.id = cbr.id " "FULL JOIN pwrx ON pwrx.id = cbr.id INNER JOIN samples ON samples.id = " "coalesce(cbr.id, mwait.id, pwre.id, exstop.id, pwrx.id) " "INNER JOIN selected_events ON selected_events.id = samples.evsel_id " "ORDER BY samples.id" ) if __name__ == "__main__": ap = argparse.ArgumentParser( description="Export perf data to a postgresql database") ap.add_argument("-i", "--input", default="perf.data", help="Input file name") ap.add_argument("-o", "--output", required=True, help="Output database name") ap.add_argument("--itrace", default=None, help="itrace options, e.g. cr") args = ap.parse_args() exporter = PostgresExporter(args.output) session = None succeeded = False caught_error = False try: exporter.setup_db() session = perf.session(perf.data(args.input), context_switch=exporter.process_context_switch, sample=exporter.process_event, call_return=exporter.process_call_return, itrace=args.itrace) exporter.session = session try: session.process_events() finally: # Break the C perf.session -> bound method -> exporter reference cycle. exporter.session = None exporter.finalize() print(f"Successfully exported to {args.output}") succeeded = True except (OSError, RuntimeError, ValueError, KeyboardInterrupt) as e: if not isinstance(e, (KeyboardInterrupt, SystemExit)): import traceback traceback.print_exc() caught_error = True finally: exporter.session = None for out_file in exporter.files.values(): try: if not out_file.closed: out_file.close() except OSError: pass exporter.disconnect() if not succeeded: if (getattr(exporter, 'created_output_dir', False) and os.path.exists(exporter.output_dir_name)): shutil.rmtree(exporter.output_dir_name, ignore_errors=True) if caught_error: sys.exit(1)