Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
ifeq ($(CONFIG_LIBTRACEEVENT),y)
perf-util-$(CONFIG_LIBPERL) += perl/Perf-Trace-Util/
endif
perf-util-$(CONFIG_LIBPYTHON) += python/Perf-Trace-Util/
ifdef MYPY
PY_TESTS := $(shell find python -type f -name '*.py')
MYPY_TEST_LOGS := $(PY_TESTS:python/%=python/%.mypy_log)
else
MYPY_TEST_LOGS :=
endif
$(OUTPUT)%.mypy_log: %
$(call rule_mkdir)
$(Q)$(call echo-cmd,test)mypy "$<" > $@ || (cat $@ && rm $@ && false)
perf-y += $(MYPY_TEST_LOGS)
ifdef PYLINT
PY_TESTS := $(shell find python -type f -name '*.py')
PYLINT_TEST_LOGS := $(PY_TESTS:python/%=python/%.pylint_log)
else
PYLINT_TEST_LOGS :=
endif
$(OUTPUT)%.pylint_log: %
$(call rule_mkdir)
$(Q)$(call echo-cmd,test)pylint "$<" > $@ || (cat $@ && rm $@ && false)
perf-y += $(PYLINT_TEST_LOGS)
@@ -0,0 +1,9 @@
perf-util-y += Context.o
CFLAGS_Context.o += $(PERL_EMBED_CCOPTS) -Wno-redundant-decls -Wno-strict-prototypes -Wno-bad-function-cast -Wno-declaration-after-statement -Wno-switch-enum
CFLAGS_Context.o += -Wno-unused-parameter -Wno-nested-externs -Wno-undef
CFLAGS_Context.o += -Wno-switch-default -Wno-shadow -Wno-thread-safety-analysis
ifeq ($(CC_NO_CLANG), 1)
CFLAGS_Context.o += -Wno-unused-command-line-argument
endif
@@ -0,0 +1,122 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* This file was generated automatically by ExtUtils::ParseXS version 2.18_02 from the
* contents of Context.xs. Do not edit this file, edit Context.xs instead.
*
* ANY CHANGES MADE HERE WILL BE LOST!
*/
#include <stdbool.h>
#ifndef HAS_BOOL
# define HAS_BOOL 1
#endif
#line 1 "Context.xs"
/*
* Context.xs. XS interfaces for perf script.
*
* Copyright (C) 2009 Tom Zanussi <tzanussi@gmail.com>
*/
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "../../../util/trace-event.h"
#ifndef PERL_UNUSED_VAR
# define PERL_UNUSED_VAR(var) if (0) var = var
#endif
#line 42 "Context.c"
XS(XS_Perf__Trace__Context_common_pc); /* prototype to pass -Wmissing-prototypes */
XS(XS_Perf__Trace__Context_common_pc)
{
#ifdef dVAR
dVAR; dXSARGS;
#else
dXSARGS;
#endif
if (items != 1)
Perl_croak(aTHX_ "Usage: %s(%s)", "Perf::Trace::Context::common_pc", "context");
PERL_UNUSED_VAR(cv); /* -W */
{
struct scripting_context * context = INT2PTR(struct scripting_context *,SvIV(ST(0)));
int RETVAL;
dXSTARG;
RETVAL = common_pc(context);
XSprePUSH; PUSHi((IV)RETVAL);
}
XSRETURN(1);
}
XS(XS_Perf__Trace__Context_common_flags); /* prototype to pass -Wmissing-prototypes */
XS(XS_Perf__Trace__Context_common_flags)
{
#ifdef dVAR
dVAR; dXSARGS;
#else
dXSARGS;
#endif
if (items != 1)
Perl_croak(aTHX_ "Usage: %s(%s)", "Perf::Trace::Context::common_flags", "context");
PERL_UNUSED_VAR(cv); /* -W */
{
struct scripting_context * context = INT2PTR(struct scripting_context *,SvIV(ST(0)));
int RETVAL;
dXSTARG;
RETVAL = common_flags(context);
XSprePUSH; PUSHi((IV)RETVAL);
}
XSRETURN(1);
}
XS(XS_Perf__Trace__Context_common_lock_depth); /* prototype to pass -Wmissing-prototypes */
XS(XS_Perf__Trace__Context_common_lock_depth)
{
#ifdef dVAR
dVAR; dXSARGS;
#else
dXSARGS;
#endif
if (items != 1)
Perl_croak(aTHX_ "Usage: %s(%s)", "Perf::Trace::Context::common_lock_depth", "context");
PERL_UNUSED_VAR(cv); /* -W */
{
struct scripting_context * context = INT2PTR(struct scripting_context *,SvIV(ST(0)));
int RETVAL;
dXSTARG;
RETVAL = common_lock_depth(context);
XSprePUSH; PUSHi((IV)RETVAL);
}
XSRETURN(1);
}
#ifdef __cplusplus
extern "C"
#endif
XS(boot_Perf__Trace__Context); /* prototype to pass -Wmissing-prototypes */
XS(boot_Perf__Trace__Context)
{
#ifdef dVAR
dVAR; dXSARGS;
#else
dXSARGS;
#endif
const char* file = __FILE__;
PERL_UNUSED_VAR(cv); /* -W */
PERL_UNUSED_VAR(items); /* -W */
XS_VERSION_BOOTCHECK ;
newXSproto("Perf::Trace::Context::common_pc", XS_Perf__Trace__Context_common_pc, file, "$");
newXSproto("Perf::Trace::Context::common_flags", XS_Perf__Trace__Context_common_flags, file, "$");
newXSproto("Perf::Trace::Context::common_lock_depth", XS_Perf__Trace__Context_common_lock_depth, file, "$");
if (PL_unitcheckav)
call_list(PL_scopestack_ix, PL_unitcheckav);
XSRETURN_YES;
}
@@ -0,0 +1,42 @@
/*
* Context.xs. XS interfaces for perf script.
*
* Copyright (C) 2009 Tom Zanussi <tzanussi@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "../../../perf.h"
#include "../../../util/trace-event.h"
MODULE = Perf::Trace::Context PACKAGE = Perf::Trace::Context
PROTOTYPES: ENABLE
int
common_pc(context)
struct scripting_context * context
int
common_flags(context)
struct scripting_context * context
int
common_lock_depth(context)
struct scripting_context * context
@@ -0,0 +1,18 @@
# SPDX-License-Identifier: GPL-2.0
use 5.010000;
use ExtUtils::MakeMaker;
# See lib/ExtUtils/MakeMaker.pm for details of how to influence
# the contents of the Makefile that is written.
WriteMakefile(
NAME => 'Perf::Trace::Context',
VERSION_FROM => 'lib/Perf/Trace/Context.pm', # finds $VERSION
PREREQ_PM => {}, # e.g., Module::Name => 1.1
($] >= 5.005 ? ## Add these new keywords supported since 5.005
(ABSTRACT_FROM => 'lib/Perf/Trace/Context.pm', # retrieve abstract from module
AUTHOR => 'Tom Zanussi <tzanussi@gmail.com>') : ()),
LIBS => [''], # e.g., '-lm'
DEFINE => '-I ../..', # e.g., '-DHAVE_SOMETHING'
INC => '-I.', # e.g., '-I. -I/usr/include/other'
# Un-comment this if you add C files to link with later:
OBJECT => 'Context.o', # link all the C files too
);
@@ -0,0 +1,59 @@
Perf-Trace-Util version 0.01
============================
This module contains utility functions for use with perf script.
Core.pm and Util.pm are pure Perl modules; Core.pm contains routines
that the core perf support for Perl calls on and should always be
'used', while Util.pm contains useful but optional utility functions
that scripts may want to use. Context.pm contains the Perl->C
interface that allows scripts to access data in the embedding perf
executable; scripts wishing to do that should 'use Context.pm'.
The Perl->C perf interface is completely driven by Context.xs. If you
want to add new Perl functions that end up accessing C data in the
perf executable, you add desciptions of the new functions here.
scripting_context is a pointer to the perf data in the perf executable
that you want to access - it's passed as the second parameter,
$context, to all handler functions.
After you do that:
perl Makefile.PL # to create a Makefile for the next step
make # to create Context.c
edit Context.c to add const to the char* file = __FILE__ line in
XS(boot_Perf__Trace__Context) to silence a warning/error.
You can delete the Makefile, object files and anything else that was
generated e.g. blib and shared library, etc, except for of course
Context.c
You should then be able to run the normal perf make as usual.
INSTALLATION
Building perf with perf script Perl scripting should install this
module in the right place.
You should make sure libperl and ExtUtils/Embed.pm are installed first
e.g. apt-get install libperl-dev or yum install perl-ExtUtils-Embed.
DEPENDENCIES
This module requires these other modules and libraries:
None
COPYRIGHT AND LICENCE
Copyright (C) 2009 by Tom Zanussi <tzanussi@gmail.com>
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.10.0 or,
at your option, any later version of Perl 5 you may have available.
Alternatively, this software may be distributed under the terms of the
GNU General Public License ("GPL") version 2 as published by the Free
Software Foundation.
@@ -0,0 +1,55 @@
package Perf::Trace::Context;
use 5.010000;
use strict;
use warnings;
require Exporter;
our @ISA = qw(Exporter);
our %EXPORT_TAGS = ( 'all' => [ qw(
) ] );
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
our @EXPORT = qw(
common_pc common_flags common_lock_depth
);
our $VERSION = '0.01';
require XSLoader;
XSLoader::load('Perf::Trace::Context', $VERSION);
1;
__END__
=head1 NAME
Perf::Trace::Context - Perl extension for accessing functions in perf.
=head1 SYNOPSIS
use Perf::Trace::Context;
=head1 SEE ALSO
Perf (script) documentation
=head1 AUTHOR
Tom Zanussi, E<lt>tzanussi@gmail.com<gt>
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2009 by Tom Zanussi
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.10.0 or,
at your option, any later version of Perl 5 you may have available.
Alternatively, this software may be distributed under the terms of the
GNU General Public License ("GPL") version 2 as published by the Free
Software Foundation.
=cut
@@ -0,0 +1,192 @@
package Perf::Trace::Core;
use 5.010000;
use strict;
use warnings;
require Exporter;
our @ISA = qw(Exporter);
our %EXPORT_TAGS = ( 'all' => [ qw(
) ] );
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
our @EXPORT = qw(
define_flag_field define_flag_value flag_str dump_flag_fields
define_symbolic_field define_symbolic_value symbol_str dump_symbolic_fields
trace_flag_str
);
our $VERSION = '0.01';
my %trace_flags = (0x00 => "NONE",
0x01 => "IRQS_OFF",
0x02 => "IRQS_NOSUPPORT",
0x04 => "NEED_RESCHED",
0x08 => "HARDIRQ",
0x10 => "SOFTIRQ");
sub trace_flag_str
{
my ($value) = @_;
my $string;
my $print_delim = 0;
foreach my $idx (sort {$a <=> $b} keys %trace_flags) {
if (!$value && !$idx) {
$string .= "NONE";
last;
}
if ($idx && ($value & $idx) == $idx) {
if ($print_delim) {
$string .= " | ";
}
$string .= "$trace_flags{$idx}";
$print_delim = 1;
$value &= ~$idx;
}
}
return $string;
}
my %flag_fields;
my %symbolic_fields;
sub flag_str
{
my ($event_name, $field_name, $value) = @_;
my $string;
if ($flag_fields{$event_name}{$field_name}) {
my $print_delim = 0;
foreach my $idx (sort {$a <=> $b} keys %{$flag_fields{$event_name}{$field_name}{"values"}}) {
if (!$value && !$idx) {
$string .= "$flag_fields{$event_name}{$field_name}{'values'}{$idx}";
last;
}
if ($idx && ($value & $idx) == $idx) {
if ($print_delim && $flag_fields{$event_name}{$field_name}{'delim'}) {
$string .= " $flag_fields{$event_name}{$field_name}{'delim'} ";
}
$string .= "$flag_fields{$event_name}{$field_name}{'values'}{$idx}";
$print_delim = 1;
$value &= ~$idx;
}
}
}
return $string;
}
sub define_flag_field
{
my ($event_name, $field_name, $delim) = @_;
$flag_fields{$event_name}{$field_name}{"delim"} = $delim;
}
sub define_flag_value
{
my ($event_name, $field_name, $value, $field_str) = @_;
$flag_fields{$event_name}{$field_name}{"values"}{$value} = $field_str;
}
sub dump_flag_fields
{
for my $event (keys %flag_fields) {
print "event $event:\n";
for my $field (keys %{$flag_fields{$event}}) {
print " field: $field:\n";
print " delim: $flag_fields{$event}{$field}{'delim'}\n";
foreach my $idx (sort {$a <=> $b} keys %{$flag_fields{$event}{$field}{"values"}}) {
print " value $idx: $flag_fields{$event}{$field}{'values'}{$idx}\n";
}
}
}
}
sub symbol_str
{
my ($event_name, $field_name, $value) = @_;
if ($symbolic_fields{$event_name}{$field_name}) {
foreach my $idx (sort {$a <=> $b} keys %{$symbolic_fields{$event_name}{$field_name}{"values"}}) {
if (!$value && !$idx) {
return "$symbolic_fields{$event_name}{$field_name}{'values'}{$idx}";
last;
}
if ($value == $idx) {
return "$symbolic_fields{$event_name}{$field_name}{'values'}{$idx}";
}
}
}
return undef;
}
sub define_symbolic_field
{
my ($event_name, $field_name) = @_;
# nothing to do, really
}
sub define_symbolic_value
{
my ($event_name, $field_name, $value, $field_str) = @_;
$symbolic_fields{$event_name}{$field_name}{"values"}{$value} = $field_str;
}
sub dump_symbolic_fields
{
for my $event (keys %symbolic_fields) {
print "event $event:\n";
for my $field (keys %{$symbolic_fields{$event}}) {
print " field: $field:\n";
foreach my $idx (sort {$a <=> $b} keys %{$symbolic_fields{$event}{$field}{"values"}}) {
print " value $idx: $symbolic_fields{$event}{$field}{'values'}{$idx}\n";
}
}
}
}
1;
__END__
=head1 NAME
Perf::Trace::Core - Perl extension for perf script
=head1 SYNOPSIS
use Perf::Trace::Core
=head1 SEE ALSO
Perf (script) documentation
=head1 AUTHOR
Tom Zanussi, E<lt>tzanussi@gmail.com<gt>
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2009 by Tom Zanussi
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.10.0 or,
at your option, any later version of Perl 5 you may have available.
Alternatively, this software may be distributed under the terms of the
GNU General Public License ("GPL") version 2 as published by the Free
Software Foundation.
=cut
@@ -0,0 +1,94 @@
package Perf::Trace::Util;
use 5.010000;
use strict;
use warnings;
require Exporter;
our @ISA = qw(Exporter);
our %EXPORT_TAGS = ( 'all' => [ qw(
) ] );
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
our @EXPORT = qw(
avg nsecs nsecs_secs nsecs_nsecs nsecs_usecs print_nsecs
clear_term
);
our $VERSION = '0.01';
sub avg
{
my ($total, $n) = @_;
return $total / $n;
}
my $NSECS_PER_SEC = 1000000000;
sub nsecs
{
my ($secs, $nsecs) = @_;
return $secs * $NSECS_PER_SEC + $nsecs;
}
sub nsecs_secs {
my ($nsecs) = @_;
return $nsecs / $NSECS_PER_SEC;
}
sub nsecs_nsecs {
my ($nsecs) = @_;
return $nsecs % $NSECS_PER_SEC;
}
sub nsecs_str {
my ($nsecs) = @_;
my $str = sprintf("%5u.%09u", nsecs_secs($nsecs), nsecs_nsecs($nsecs));
return $str;
}
sub clear_term
{
print "\x1b[H\x1b[2J";
}
1;
__END__
=head1 NAME
Perf::Trace::Util - Perl extension for perf script
=head1 SYNOPSIS
use Perf::Trace::Util;
=head1 SEE ALSO
Perf (script) documentation
=head1 AUTHOR
Tom Zanussi, E<lt>tzanussi@gmail.com<gt>
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2009 by Tom Zanussi
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.10.0 or,
at your option, any later version of Perl 5 you may have available.
Alternatively, this software may be distributed under the terms of the
GNU General Public License ("GPL") version 2 as published by the Free
Software Foundation.
=cut
@@ -0,0 +1 @@
struct scripting_context * T_PTR
@@ -0,0 +1,2 @@
#!/bin/bash
perf record -a -e kmem:kmalloc -e irq:softirq_entry -e kmem:kfree
@@ -0,0 +1,3 @@
#!/bin/bash
(perf record -e raw_syscalls:sys_exit $@ || \
perf record -e syscalls:sys_exit $@) 2> /dev/null
@@ -0,0 +1,10 @@
#!/bin/bash
# description: system-wide failed syscalls
# args: [comm]
if [ $# -gt 0 ] ; then
if ! expr match "$1" "-" > /dev/null ; then
comm=$1
shift
fi
fi
perf script $@ -s "$PERF_EXEC_PATH"/scripts/perl/failed-syscalls.pl $comm
@@ -0,0 +1,3 @@
#!/bin/bash
perf record -e syscalls:sys_enter_read -e syscalls:sys_enter_write $@
@@ -0,0 +1,10 @@
#!/bin/bash
# description: r/w activity for a program, by file
# args: <comm>
if [ $# -lt 1 ] ; then
echo "usage: rw-by-file <comm>"
exit
fi
comm=$1
shift
perf script $@ -s "$PERF_EXEC_PATH"/scripts/perl/rw-by-file.pl $comm
@@ -0,0 +1,2 @@
#!/bin/bash
perf record -e syscalls:sys_enter_read -e syscalls:sys_exit_read -e syscalls:sys_enter_write -e syscalls:sys_exit_write $@
@@ -0,0 +1,3 @@
#!/bin/bash
# description: system-wide r/w activity
perf script $@ -s "$PERF_EXEC_PATH"/scripts/perl/rw-by-pid.pl
@@ -0,0 +1,2 @@
#!/bin/bash
perf record -e syscalls:sys_enter_read -e syscalls:sys_exit_read -e syscalls:sys_enter_write -e syscalls:sys_exit_write $@
@@ -0,0 +1,20 @@
#!/bin/bash
# description: system-wide r/w top
# args: [interval]
n_args=0
for i in "$@"
do
if expr match "$i" "-" > /dev/null ; then
break
fi
n_args=$(( $n_args + 1 ))
done
if [ "$n_args" -gt 1 ] ; then
echo "usage: rwtop-report [interval]"
exit
fi
if [ "$n_args" -gt 0 ] ; then
interval=$1
shift
fi
perf script $@ -s "$PERF_EXEC_PATH"/scripts/perl/rwtop.pl $interval
@@ -0,0 +1,6 @@
#!/bin/bash
perf record -e sched:sched_switch -e sched:sched_wakeup $@
@@ -0,0 +1,3 @@
#!/bin/bash
# description: system-wide min/max/avg wakeup latency
perf script $@ -s "$PERF_EXEC_PATH"/scripts/perl/wakeup-latency.pl
@@ -0,0 +1,106 @@
# perf script event handlers, generated by perf script -g perl
# (c) 2009, Tom Zanussi <tzanussi@gmail.com>
# Licensed under the terms of the GNU GPL License version 2
# This script tests basic functionality such as flag and symbol
# strings, common_xxx() calls back into perf, begin, end, unhandled
# events, etc. Basically, if this script runs successfully and
# displays expected results, perl scripting support should be ok.
use lib "$ENV{'PERF_EXEC_PATH'}/scripts/perl/Perf-Trace-Util/lib";
use lib "./Perf-Trace-Util/lib";
use Perf::Trace::Core;
use Perf::Trace::Context;
use Perf::Trace::Util;
sub trace_begin
{
print "trace_begin\n";
}
sub trace_end
{
print "trace_end\n";
print_unhandled();
}
sub irq::softirq_entry
{
my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
$common_pid, $common_comm, $common_callchain,
$vec) = @_;
print_header($event_name, $common_cpu, $common_secs, $common_nsecs,
$common_pid, $common_comm);
print_uncommon($context);
printf("vec=%s\n",
symbol_str("irq::softirq_entry", "vec", $vec));
}
sub kmem::kmalloc
{
my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
$common_pid, $common_comm, $common_callchain,
$call_site, $ptr, $bytes_req, $bytes_alloc,
$gfp_flags) = @_;
print_header($event_name, $common_cpu, $common_secs, $common_nsecs,
$common_pid, $common_comm);
print_uncommon($context);
printf("call_site=%p, ptr=%p, bytes_req=%u, bytes_alloc=%u, ".
"gfp_flags=%s\n",
$call_site, $ptr, $bytes_req, $bytes_alloc,
flag_str("kmem::kmalloc", "gfp_flags", $gfp_flags));
}
# print trace fields not included in handler args
sub print_uncommon
{
my ($context) = @_;
printf("common_preempt_count=%d, common_flags=%s, common_lock_depth=%d, ",
common_pc($context), trace_flag_str(common_flags($context)),
common_lock_depth($context));
}
my %unhandled;
sub print_unhandled
{
if ((scalar keys %unhandled) == 0) {
return;
}
print "\nunhandled events:\n\n";
printf("%-40s %10s\n", "event", "count");
printf("%-40s %10s\n", "----------------------------------------",
"-----------");
foreach my $event_name (keys %unhandled) {
printf("%-40s %10d\n", $event_name, $unhandled{$event_name});
}
}
sub trace_unhandled
{
my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
$common_pid, $common_comm, $common_callchain) = @_;
$unhandled{$event_name}++;
}
sub print_header
{
my ($event_name, $cpu, $secs, $nsecs, $pid, $comm) = @_;
printf("%-20s %5u %05u.%09u %8u %-20s ",
$event_name, $cpu, $secs, $nsecs, $pid, $comm);
}
@@ -0,0 +1,47 @@
# failed system call counts
# (c) 2010, Tom Zanussi <tzanussi@gmail.com>
# Licensed under the terms of the GNU GPL License version 2
#
# Displays system-wide failed system call totals
# If a [comm] arg is specified, only syscalls called by [comm] are displayed.
use lib "$ENV{'PERF_EXEC_PATH'}/scripts/perl/Perf-Trace-Util/lib";
use lib "./Perf-Trace-Util/lib";
use Perf::Trace::Core;
use Perf::Trace::Context;
use Perf::Trace::Util;
my $for_comm = shift;
my %failed_syscalls;
sub raw_syscalls::sys_exit
{
my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
$common_pid, $common_comm, $common_callchain,
$id, $ret) = @_;
if ($ret < 0) {
$failed_syscalls{$common_comm}++;
}
}
sub syscalls::sys_exit
{
raw_syscalls::sys_exit(@_)
}
sub trace_end
{
printf("\nfailed syscalls by comm:\n\n");
printf("%-20s %10s\n", "comm", "# errors");
printf("%-20s %6s %10s\n", "--------------------", "----------");
foreach my $comm (sort {$failed_syscalls{$b} <=> $failed_syscalls{$a}}
keys %failed_syscalls) {
next if ($for_comm && $comm ne $for_comm);
printf("%-20s %10s\n", $comm, $failed_syscalls{$comm});
}
}
@@ -0,0 +1,106 @@
#!/usr/bin/perl -w
# SPDX-License-Identifier: GPL-2.0-only
# (c) 2009, Tom Zanussi <tzanussi@gmail.com>
# Display r/w activity for files read/written to for a given program
# The common_* event handler fields are the most useful fields common to
# all events. They don't necessarily correspond to the 'common_*' fields
# in the status files. Those fields not available as handler params can
# be retrieved via script functions of the form get_common_*().
use 5.010000;
use strict;
use warnings;
use lib "$ENV{'PERF_EXEC_PATH'}/scripts/perl/Perf-Trace-Util/lib";
use lib "./Perf-Trace-Util/lib";
use Perf::Trace::Core;
use Perf::Trace::Util;
my $usage = "perf script -s rw-by-file.pl <comm>\n";
my $for_comm = shift or die $usage;
my %reads;
my %writes;
sub syscalls::sys_enter_read
{
my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
$common_pid, $common_comm, $common_callchain, $nr, $fd, $buf, $count) = @_;
if ($common_comm eq $for_comm) {
$reads{$fd}{bytes_requested} += $count;
$reads{$fd}{total_reads}++;
}
}
sub syscalls::sys_enter_write
{
my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
$common_pid, $common_comm, $common_callchain, $nr, $fd, $buf, $count) = @_;
if ($common_comm eq $for_comm) {
$writes{$fd}{bytes_written} += $count;
$writes{$fd}{total_writes}++;
}
}
sub trace_end
{
printf("file read counts for $for_comm:\n\n");
printf("%6s %10s %10s\n", "fd", "# reads", "bytes_requested");
printf("%6s %10s %10s\n", "------", "----------", "-----------");
foreach my $fd (sort {$reads{$b}{bytes_requested} <=>
$reads{$a}{bytes_requested}} keys %reads) {
my $total_reads = $reads{$fd}{total_reads};
my $bytes_requested = $reads{$fd}{bytes_requested};
printf("%6u %10u %10u\n", $fd, $total_reads, $bytes_requested);
}
printf("\nfile write counts for $for_comm:\n\n");
printf("%6s %10s %10s\n", "fd", "# writes", "bytes_written");
printf("%6s %10s %10s\n", "------", "----------", "-----------");
foreach my $fd (sort {$writes{$b}{bytes_written} <=>
$writes{$a}{bytes_written}} keys %writes) {
my $total_writes = $writes{$fd}{total_writes};
my $bytes_written = $writes{$fd}{bytes_written};
printf("%6u %10u %10u\n", $fd, $total_writes, $bytes_written);
}
print_unhandled();
}
my %unhandled;
sub print_unhandled
{
if ((scalar keys %unhandled) == 0) {
return;
}
print "\nunhandled events:\n\n";
printf("%-40s %10s\n", "event", "count");
printf("%-40s %10s\n", "----------------------------------------",
"-----------");
foreach my $event_name (keys %unhandled) {
printf("%-40s %10d\n", $event_name, $unhandled{$event_name});
}
}
sub trace_unhandled
{
my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
$common_pid, $common_comm, $common_callchain) = @_;
$unhandled{$event_name}++;
}
@@ -0,0 +1,184 @@
#!/usr/bin/perl -w
# SPDX-License-Identifier: GPL-2.0-only
# (c) 2009, Tom Zanussi <tzanussi@gmail.com>
# Display r/w activity for all processes
# The common_* event handler fields are the most useful fields common to
# all events. They don't necessarily correspond to the 'common_*' fields
# in the status files. Those fields not available as handler params can
# be retrieved via script functions of the form get_common_*().
use 5.010000;
use strict;
use warnings;
use lib "$ENV{'PERF_EXEC_PATH'}/scripts/perl/Perf-Trace-Util/lib";
use lib "./Perf-Trace-Util/lib";
use Perf::Trace::Core;
use Perf::Trace::Util;
my %reads;
my %writes;
sub syscalls::sys_exit_read
{
my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
$common_pid, $common_comm, $common_callchain,
$nr, $ret) = @_;
if ($ret > 0) {
$reads{$common_pid}{bytes_read} += $ret;
} else {
if (!defined ($reads{$common_pid}{bytes_read})) {
$reads{$common_pid}{bytes_read} = 0;
}
$reads{$common_pid}{errors}{$ret}++;
}
}
sub syscalls::sys_enter_read
{
my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
$common_pid, $common_comm, $common_callchain,
$nr, $fd, $buf, $count) = @_;
$reads{$common_pid}{bytes_requested} += $count;
$reads{$common_pid}{total_reads}++;
$reads{$common_pid}{comm} = $common_comm;
}
sub syscalls::sys_exit_write
{
my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
$common_pid, $common_comm, $common_callchain,
$nr, $ret) = @_;
if ($ret <= 0) {
$writes{$common_pid}{errors}{$ret}++;
}
}
sub syscalls::sys_enter_write
{
my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
$common_pid, $common_comm, $common_callchain,
$nr, $fd, $buf, $count) = @_;
$writes{$common_pid}{bytes_written} += $count;
$writes{$common_pid}{total_writes}++;
$writes{$common_pid}{comm} = $common_comm;
}
sub trace_end
{
printf("read counts by pid:\n\n");
printf("%6s %20s %10s %10s %10s\n", "pid", "comm",
"# reads", "bytes_requested", "bytes_read");
printf("%6s %-20s %10s %10s %10s\n", "------", "--------------------",
"-----------", "----------", "----------");
foreach my $pid (sort { ($reads{$b}{bytes_read} || 0) <=>
($reads{$a}{bytes_read} || 0) } keys %reads) {
my $comm = $reads{$pid}{comm} || "";
my $total_reads = $reads{$pid}{total_reads} || 0;
my $bytes_requested = $reads{$pid}{bytes_requested} || 0;
my $bytes_read = $reads{$pid}{bytes_read} || 0;
printf("%6s %-20s %10s %10s %10s\n", $pid, $comm,
$total_reads, $bytes_requested, $bytes_read);
}
printf("\nfailed reads by pid:\n\n");
printf("%6s %20s %6s %10s\n", "pid", "comm", "error #", "# errors");
printf("%6s %20s %6s %10s\n", "------", "--------------------",
"------", "----------");
my @errcounts = ();
foreach my $pid (keys %reads) {
foreach my $error (keys %{$reads{$pid}{errors}}) {
my $comm = $reads{$pid}{comm} || "";
my $errcount = $reads{$pid}{errors}{$error} || 0;
push @errcounts, [$pid, $comm, $error, $errcount];
}
}
@errcounts = sort { $b->[3] <=> $a->[3] } @errcounts;
for my $i (0 .. $#errcounts) {
printf("%6d %-20s %6d %10s\n", $errcounts[$i][0],
$errcounts[$i][1], $errcounts[$i][2], $errcounts[$i][3]);
}
printf("\nwrite counts by pid:\n\n");
printf("%6s %20s %10s %10s\n", "pid", "comm",
"# writes", "bytes_written");
printf("%6s %-20s %10s %10s\n", "------", "--------------------",
"-----------", "----------");
foreach my $pid (sort { ($writes{$b}{bytes_written} || 0) <=>
($writes{$a}{bytes_written} || 0)} keys %writes) {
my $comm = $writes{$pid}{comm} || "";
my $total_writes = $writes{$pid}{total_writes} || 0;
my $bytes_written = $writes{$pid}{bytes_written} || 0;
printf("%6s %-20s %10s %10s\n", $pid, $comm,
$total_writes, $bytes_written);
}
printf("\nfailed writes by pid:\n\n");
printf("%6s %20s %6s %10s\n", "pid", "comm", "error #", "# errors");
printf("%6s %20s %6s %10s\n", "------", "--------------------",
"------", "----------");
@errcounts = ();
foreach my $pid (keys %writes) {
foreach my $error (keys %{$writes{$pid}{errors}}) {
my $comm = $writes{$pid}{comm} || "";
my $errcount = $writes{$pid}{errors}{$error} || 0;
push @errcounts, [$pid, $comm, $error, $errcount];
}
}
@errcounts = sort { $b->[3] <=> $a->[3] } @errcounts;
for my $i (0 .. $#errcounts) {
printf("%6d %-20s %6d %10s\n", $errcounts[$i][0],
$errcounts[$i][1], $errcounts[$i][2], $errcounts[$i][3]);
}
print_unhandled();
}
my %unhandled;
sub print_unhandled
{
if ((scalar keys %unhandled) == 0) {
return;
}
print "\nunhandled events:\n\n";
printf("%-40s %10s\n", "event", "count");
printf("%-40s %10s\n", "----------------------------------------",
"-----------");
foreach my $event_name (keys %unhandled) {
printf("%-40s %10d\n", $event_name, $unhandled{$event_name});
}
}
sub trace_unhandled
{
my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
$common_pid, $common_comm, $common_callchain) = @_;
$unhandled{$event_name}++;
}
@@ -0,0 +1,203 @@
#!/usr/bin/perl -w
# SPDX-License-Identifier: GPL-2.0-only
# (c) 2010, Tom Zanussi <tzanussi@gmail.com>
# read/write top
#
# Periodically displays system-wide r/w call activity, broken down by
# pid. If an [interval] arg is specified, the display will be
# refreshed every [interval] seconds. The default interval is 3
# seconds.
use 5.010000;
use strict;
use warnings;
use lib "$ENV{'PERF_EXEC_PATH'}/scripts/perl/Perf-Trace-Util/lib";
use lib "./Perf-Trace-Util/lib";
use Perf::Trace::Core;
use Perf::Trace::Util;
use POSIX qw/SIGALRM SA_RESTART/;
my $default_interval = 3;
my $nlines = 20;
my $print_thread;
my $print_pending = 0;
my %reads;
my %writes;
my $interval = shift;
if (!$interval) {
$interval = $default_interval;
}
sub syscalls::sys_exit_read
{
my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
$common_pid, $common_comm, $common_callchain,
$nr, $ret) = @_;
print_check();
if ($ret > 0) {
$reads{$common_pid}{bytes_read} += $ret;
} else {
if (!defined ($reads{$common_pid}{bytes_read})) {
$reads{$common_pid}{bytes_read} = 0;
}
$reads{$common_pid}{errors}{$ret}++;
}
}
sub syscalls::sys_enter_read
{
my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
$common_pid, $common_comm, $common_callchain,
$nr, $fd, $buf, $count) = @_;
print_check();
$reads{$common_pid}{bytes_requested} += $count;
$reads{$common_pid}{total_reads}++;
$reads{$common_pid}{comm} = $common_comm;
}
sub syscalls::sys_exit_write
{
my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
$common_pid, $common_comm, $common_callchain,
$nr, $ret) = @_;
print_check();
if ($ret <= 0) {
$writes{$common_pid}{errors}{$ret}++;
}
}
sub syscalls::sys_enter_write
{
my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
$common_pid, $common_comm, $common_callchain,
$nr, $fd, $buf, $count) = @_;
print_check();
$writes{$common_pid}{bytes_written} += $count;
$writes{$common_pid}{total_writes}++;
$writes{$common_pid}{comm} = $common_comm;
}
sub trace_begin
{
my $sa = POSIX::SigAction->new(\&set_print_pending);
$sa->flags(SA_RESTART);
$sa->safe(1);
POSIX::sigaction(SIGALRM, $sa) or die "Can't set SIGALRM handler: $!\n";
alarm 1;
}
sub trace_end
{
print_unhandled();
print_totals();
}
sub print_check()
{
if ($print_pending == 1) {
$print_pending = 0;
print_totals();
}
}
sub set_print_pending()
{
$print_pending = 1;
alarm $interval;
}
sub print_totals
{
my $count;
$count = 0;
clear_term();
printf("\nread counts by pid:\n\n");
printf("%6s %20s %10s %10s %10s\n", "pid", "comm",
"# reads", "bytes_req", "bytes_read");
printf("%6s %-20s %10s %10s %10s\n", "------", "--------------------",
"----------", "----------", "----------");
foreach my $pid (sort { ($reads{$b}{bytes_read} || 0) <=>
($reads{$a}{bytes_read} || 0) } keys %reads) {
my $comm = $reads{$pid}{comm} || "";
my $total_reads = $reads{$pid}{total_reads} || 0;
my $bytes_requested = $reads{$pid}{bytes_requested} || 0;
my $bytes_read = $reads{$pid}{bytes_read} || 0;
printf("%6s %-20s %10s %10s %10s\n", $pid, $comm,
$total_reads, $bytes_requested, $bytes_read);
if (++$count == $nlines) {
last;
}
}
$count = 0;
printf("\nwrite counts by pid:\n\n");
printf("%6s %20s %10s %13s\n", "pid", "comm",
"# writes", "bytes_written");
printf("%6s %-20s %10s %13s\n", "------", "--------------------",
"----------", "-------------");
foreach my $pid (sort { ($writes{$b}{bytes_written} || 0) <=>
($writes{$a}{bytes_written} || 0)} keys %writes) {
my $comm = $writes{$pid}{comm} || "";
my $total_writes = $writes{$pid}{total_writes} || 0;
my $bytes_written = $writes{$pid}{bytes_written} || 0;
printf("%6s %-20s %10s %13s\n", $pid, $comm,
$total_writes, $bytes_written);
if (++$count == $nlines) {
last;
}
}
%reads = ();
%writes = ();
}
my %unhandled;
sub print_unhandled
{
if ((scalar keys %unhandled) == 0) {
return;
}
print "\nunhandled events:\n\n";
printf("%-40s %10s\n", "event", "count");
printf("%-40s %10s\n", "----------------------------------------",
"-----------");
foreach my $event_name (keys %unhandled) {
printf("%-40s %10d\n", $event_name, $unhandled{$event_name});
}
}
sub trace_unhandled
{
my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
$common_pid, $common_comm, $common_callchain) = @_;
$unhandled{$event_name}++;
}
@@ -0,0 +1,107 @@
#!/usr/bin/perl -w
# SPDX-License-Identifier: GPL-2.0-only
# (c) 2009, Tom Zanussi <tzanussi@gmail.com>
# Display avg/min/max wakeup latency
# The common_* event handler fields are the most useful fields common to
# all events. They don't necessarily correspond to the 'common_*' fields
# in the status files. Those fields not available as handler params can
# be retrieved via script functions of the form get_common_*().
use 5.010000;
use strict;
use warnings;
use lib "$ENV{'PERF_EXEC_PATH'}/scripts/perl/Perf-Trace-Util/lib";
use lib "./Perf-Trace-Util/lib";
use Perf::Trace::Core;
use Perf::Trace::Util;
my %last_wakeup;
my $max_wakeup_latency;
my $min_wakeup_latency;
my $total_wakeup_latency = 0;
my $total_wakeups = 0;
sub sched::sched_switch
{
my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
$common_pid, $common_comm, $common_callchain,
$prev_comm, $prev_pid, $prev_prio, $prev_state, $next_comm, $next_pid,
$next_prio) = @_;
my $wakeup_ts = $last_wakeup{$common_cpu}{ts};
if ($wakeup_ts) {
my $switch_ts = nsecs($common_secs, $common_nsecs);
my $wakeup_latency = $switch_ts - $wakeup_ts;
if ($wakeup_latency > $max_wakeup_latency) {
$max_wakeup_latency = $wakeup_latency;
}
if ($wakeup_latency < $min_wakeup_latency) {
$min_wakeup_latency = $wakeup_latency;
}
$total_wakeup_latency += $wakeup_latency;
$total_wakeups++;
}
$last_wakeup{$common_cpu}{ts} = 0;
}
sub sched::sched_wakeup
{
my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
$common_pid, $common_comm, $common_callchain,
$comm, $pid, $prio, $success, $target_cpu) = @_;
$last_wakeup{$target_cpu}{ts} = nsecs($common_secs, $common_nsecs);
}
sub trace_begin
{
$min_wakeup_latency = 1000000000;
$max_wakeup_latency = 0;
}
sub trace_end
{
printf("wakeup_latency stats:\n\n");
print "total_wakeups: $total_wakeups\n";
if ($total_wakeups) {
printf("avg_wakeup_latency (ns): %u\n",
avg($total_wakeup_latency, $total_wakeups));
} else {
printf("avg_wakeup_latency (ns): N/A\n");
}
printf("min_wakeup_latency (ns): %u\n", $min_wakeup_latency);
printf("max_wakeup_latency (ns): %u\n", $max_wakeup_latency);
print_unhandled();
}
my %unhandled;
sub print_unhandled
{
if ((scalar keys %unhandled) == 0) {
return;
}
print "\nunhandled events:\n\n";
printf("%-40s %10s\n", "event", "count");
printf("%-40s %10s\n", "----------------------------------------",
"-----------");
foreach my $event_name (keys %unhandled) {
printf("%-40s %10d\n", $event_name, $unhandled{$event_name});
}
}
sub trace_unhandled
{
my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
$common_pid, $common_comm, $common_callchain) = @_;
$unhandled{$event_name}++;
}
@@ -0,0 +1,4 @@
perf-util-y += Context.o
# -Wno-declaration-after-statement: The python headers have mixed code with declarations (decls after asserts, for instance)
CFLAGS_Context.o += $(PYTHON_EMBED_CCOPTS) -Wno-redundant-decls -Wno-strict-prototypes -Wno-unused-parameter -Wno-nested-externs -Wno-declaration-after-statement
@@ -0,0 +1,225 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Context.c. Python interfaces for perf script.
*
* Copyright (C) 2010 Tom Zanussi <tzanussi@gmail.com>
*/
/*
* Use Py_ssize_t for '#' formats to avoid DeprecationWarning: PY_SSIZE_T_CLEAN
* will be required for '#' formats.
*/
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "../../../util/config.h"
#include "../../../util/trace-event.h"
#include "../../../util/event.h"
#include "../../../util/symbol.h"
#include "../../../util/thread.h"
#include "../../../util/map.h"
#include "../../../util/maps.h"
#include "../../../util/auxtrace.h"
#include "../../../util/session.h"
#include "../../../util/srcline.h"
#include "../../../util/srccode.h"
#define _PyCapsule_GetPointer(arg1, arg2) \
PyCapsule_GetPointer((arg1), (arg2))
#define _PyBytes_FromStringAndSize(arg1, arg2) \
PyBytes_FromStringAndSize((arg1), (arg2))
#define _PyUnicode_AsUTF8(arg) \
PyUnicode_AsUTF8(arg)
PyMODINIT_FUNC PyInit_perf_trace_context(void);
static struct scripting_context *get_args(PyObject *args, const char *name, PyObject **arg2)
{
int cnt = 1 + !!arg2;
PyObject *context;
if (!PyArg_UnpackTuple(args, name, 1, cnt, &context, arg2))
return NULL;
return _PyCapsule_GetPointer(context, NULL);
}
static struct scripting_context *get_scripting_context(PyObject *args)
{
return get_args(args, "context", NULL);
}
#ifdef HAVE_LIBTRACEEVENT
static PyObject *perf_trace_context_common_pc(PyObject *obj, PyObject *args)
{
struct scripting_context *c = get_scripting_context(args);
if (!c)
return NULL;
return Py_BuildValue("i", common_pc(c));
}
static PyObject *perf_trace_context_common_flags(PyObject *obj,
PyObject *args)
{
struct scripting_context *c = get_scripting_context(args);
if (!c)
return NULL;
return Py_BuildValue("i", common_flags(c));
}
static PyObject *perf_trace_context_common_lock_depth(PyObject *obj,
PyObject *args)
{
struct scripting_context *c = get_scripting_context(args);
if (!c)
return NULL;
return Py_BuildValue("i", common_lock_depth(c));
}
#endif
static PyObject *perf_sample_insn(PyObject *obj, PyObject *args)
{
struct scripting_context *c = get_scripting_context(args);
if (!c)
return NULL;
if (c->sample->ip && !c->sample->insn_len && thread__maps(c->al->thread)) {
struct machine *machine = maps__machine(thread__maps(c->al->thread));
perf_sample__fetch_insn(c->sample, c->al->thread, machine);
}
if (!c->sample->insn_len)
Py_RETURN_NONE; /* N.B. This is a return statement */
return _PyBytes_FromStringAndSize(c->sample->insn, c->sample->insn_len);
}
static PyObject *perf_set_itrace_options(PyObject *obj, PyObject *args)
{
struct scripting_context *c;
const char *itrace_options;
int retval = -1;
PyObject *str;
c = get_args(args, "itrace_options", &str);
if (!c)
return NULL;
if (!c->session || !c->session->itrace_synth_opts)
goto out;
if (c->session->itrace_synth_opts->set) {
retval = 1;
goto out;
}
itrace_options = _PyUnicode_AsUTF8(str);
retval = itrace_do_parse_synth_opts(c->session->itrace_synth_opts, itrace_options, 0);
out:
return Py_BuildValue("i", retval);
}
static PyObject *perf_sample_src(PyObject *obj, PyObject *args, bool get_srccode)
{
struct scripting_context *c = get_scripting_context(args);
unsigned int line = 0;
char *srcfile = NULL;
char *srccode = NULL;
PyObject *result;
struct map *map;
struct dso *dso;
int len = 0;
u64 addr;
if (!c)
return NULL;
map = c->al->map;
addr = c->al->addr;
dso = map ? map__dso(map) : NULL;
if (dso)
srcfile = get_srcline_split(dso, map__rip_2objdump(map, addr), &line);
if (get_srccode) {
if (srcfile)
srccode = find_sourceline(srcfile, line, &len);
result = Py_BuildValue("(sIs#)", srcfile, line, srccode, (Py_ssize_t)len);
} else {
result = Py_BuildValue("(sI)", srcfile, line);
}
free(srcfile);
return result;
}
static PyObject *perf_sample_srcline(PyObject *obj, PyObject *args)
{
return perf_sample_src(obj, args, false);
}
static PyObject *perf_sample_srccode(PyObject *obj, PyObject *args)
{
return perf_sample_src(obj, args, true);
}
static PyObject *__perf_config_get(PyObject *obj, PyObject *args)
{
const char *config_name;
if (!PyArg_ParseTuple(args, "s", &config_name))
return NULL;
return Py_BuildValue("s", perf_config_get(config_name));
}
static PyMethodDef ContextMethods[] = {
#ifdef HAVE_LIBTRACEEVENT
{ "common_pc", perf_trace_context_common_pc, METH_VARARGS,
"Get the common preempt count event field value."},
{ "common_flags", perf_trace_context_common_flags, METH_VARARGS,
"Get the common flags event field value."},
{ "common_lock_depth", perf_trace_context_common_lock_depth,
METH_VARARGS, "Get the common lock depth event field value."},
#endif
{ "perf_sample_insn", perf_sample_insn,
METH_VARARGS, "Get the machine code instruction."},
{ "perf_set_itrace_options", perf_set_itrace_options,
METH_VARARGS, "Set --itrace options."},
{ "perf_sample_srcline", perf_sample_srcline,
METH_VARARGS, "Get source file name and line number."},
{ "perf_sample_srccode", perf_sample_srccode,
METH_VARARGS, "Get source file name, line number and line."},
{ "perf_config_get", __perf_config_get, METH_VARARGS, "Get perf config entry"},
{ NULL, NULL, 0, NULL}
};
PyMODINIT_FUNC PyInit_perf_trace_context(void)
{
static struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT,
"perf_trace_context", /* m_name */
"", /* m_doc */
-1, /* m_size */
ContextMethods, /* m_methods */
NULL, /* m_reload */
NULL, /* m_traverse */
NULL, /* m_clear */
NULL, /* m_free */
};
PyObject *mod;
mod = PyModule_Create(&moduledef);
/* Add perf_script_context to the module so it can be imported */
PyObject_SetAttrString(mod, "perf_script_context", Py_None);
return mod;
}
@@ -0,0 +1,116 @@
# Core.py - Python extension for perf script, core functions
#
# Copyright (C) 2010 by Tom Zanussi <tzanussi@gmail.com>
#
# This software may be distributed under the terms of the GNU General
# Public License ("GPL") version 2 as published by the Free Software
# Foundation.
from collections import defaultdict
def autodict():
return defaultdict(autodict)
flag_fields = autodict()
symbolic_fields = autodict()
def define_flag_field(event_name, field_name, delim):
flag_fields[event_name][field_name]['delim'] = delim
def define_flag_value(event_name, field_name, value, field_str):
flag_fields[event_name][field_name]['values'][value] = field_str
def define_symbolic_field(event_name, field_name):
# nothing to do, really
pass
def define_symbolic_value(event_name, field_name, value, field_str):
symbolic_fields[event_name][field_name]['values'][value] = field_str
def flag_str(event_name, field_name, value):
string = ""
if flag_fields[event_name][field_name]:
print_delim = 0
for idx in sorted(flag_fields[event_name][field_name]['values']):
if not value and not idx:
string += flag_fields[event_name][field_name]['values'][idx]
break
if idx and (value & idx) == idx:
if print_delim and flag_fields[event_name][field_name]['delim']:
string += " " + flag_fields[event_name][field_name]['delim'] + " "
string += flag_fields[event_name][field_name]['values'][idx]
print_delim = 1
value &= ~idx
return string
def symbol_str(event_name, field_name, value):
string = ""
if symbolic_fields[event_name][field_name]:
for idx in sorted(symbolic_fields[event_name][field_name]['values']):
if not value and not idx:
string = symbolic_fields[event_name][field_name]['values'][idx]
break
if (value == idx):
string = symbolic_fields[event_name][field_name]['values'][idx]
break
return string
trace_flags = { 0x00: "NONE", \
0x01: "IRQS_OFF", \
0x02: "IRQS_NOSUPPORT", \
0x04: "NEED_RESCHED", \
0x08: "HARDIRQ", \
0x10: "SOFTIRQ" }
def trace_flag_str(value):
string = ""
print_delim = 0
for idx in trace_flags:
if not value and not idx:
string += "NONE"
break
if idx and (value & idx) == idx:
if print_delim:
string += " | ";
string += trace_flags[idx]
print_delim = 1
value &= ~idx
return string
def taskState(state):
states = {
0 : "R",
1 : "S",
2 : "D",
64: "DEAD"
}
if state not in states:
return "Unknown"
return states[state]
class EventHeaders:
def __init__(self, common_cpu, common_secs, common_nsecs,
common_pid, common_comm, common_callchain):
self.cpu = common_cpu
self.secs = common_secs
self.nsecs = common_nsecs
self.pid = common_pid
self.comm = common_comm
self.callchain = common_callchain
def ts(self):
return (self.secs * (10 ** 9)) + self.nsecs
def ts_format(self):
return "%d.%d" % (self.secs, int(self.nsecs / 1000))
@@ -0,0 +1,97 @@
# EventClass.py
# SPDX-License-Identifier: GPL-2.0
#
# This is a library defining some events types classes, which could
# be used by other scripts to analyzing the perf samples.
#
# Currently there are just a few classes defined for examples,
# PerfEvent is the base class for all perf event sample, PebsEvent
# is a HW base Intel x86 PEBS event, and user could add more SW/HW
# event classes based on requirements.
from __future__ import print_function
import struct
# Event types, user could add more here
EVTYPE_GENERIC = 0
EVTYPE_PEBS = 1 # Basic PEBS event
EVTYPE_PEBS_LL = 2 # PEBS event with load latency info
EVTYPE_IBS = 3
#
# Currently we don't have good way to tell the event type, but by
# the size of raw buffer, raw PEBS event with load latency data's
# size is 176 bytes, while the pure PEBS event's size is 144 bytes.
#
def create_event(name, comm, dso, symbol, raw_buf):
if (len(raw_buf) == 144):
event = PebsEvent(name, comm, dso, symbol, raw_buf)
elif (len(raw_buf) == 176):
event = PebsNHM(name, comm, dso, symbol, raw_buf)
else:
event = PerfEvent(name, comm, dso, symbol, raw_buf)
return event
class PerfEvent(object):
event_num = 0
def __init__(self, name, comm, dso, symbol, raw_buf, ev_type=EVTYPE_GENERIC):
self.name = name
self.comm = comm
self.dso = dso
self.symbol = symbol
self.raw_buf = raw_buf
self.ev_type = ev_type
PerfEvent.event_num += 1
def show(self):
print("PMU event: name=%12s, symbol=%24s, comm=%8s, dso=%12s" %
(self.name, self.symbol, self.comm, self.dso))
#
# Basic Intel PEBS (Precise Event-based Sampling) event, whose raw buffer
# contains the context info when that event happened: the EFLAGS and
# linear IP info, as well as all the registers.
#
class PebsEvent(PerfEvent):
pebs_num = 0
def __init__(self, name, comm, dso, symbol, raw_buf, ev_type=EVTYPE_PEBS):
tmp_buf=raw_buf[0:80]
flags, ip, ax, bx, cx, dx, si, di, bp, sp = struct.unpack('QQQQQQQQQQ', tmp_buf)
self.flags = flags
self.ip = ip
self.ax = ax
self.bx = bx
self.cx = cx
self.dx = dx
self.si = si
self.di = di
self.bp = bp
self.sp = sp
PerfEvent.__init__(self, name, comm, dso, symbol, raw_buf, ev_type)
PebsEvent.pebs_num += 1
del tmp_buf
#
# Intel Nehalem and Westmere support PEBS plus Load Latency info which lie
# in the four 64 bit words write after the PEBS data:
# Status: records the IA32_PERF_GLOBAL_STATUS register value
# DLA: Data Linear Address (EIP)
# DSE: Data Source Encoding, where the latency happens, hit or miss
# in L1/L2/L3 or IO operations
# LAT: the actual latency in cycles
#
class PebsNHM(PebsEvent):
pebs_nhm_num = 0
def __init__(self, name, comm, dso, symbol, raw_buf, ev_type=EVTYPE_PEBS_LL):
tmp_buf=raw_buf[144:176]
status, dla, dse, lat = struct.unpack('QQQQ', tmp_buf)
self.status = status
self.dla = dla
self.dse = dse
self.lat = lat
PebsEvent.__init__(self, name, comm, dso, symbol, raw_buf, ev_type)
PebsNHM.pebs_nhm_num += 1
del tmp_buf
@@ -0,0 +1,184 @@
# SchedGui.py - Python extension for perf script, basic GUI code for
# traces drawing and overview.
#
# Copyright (C) 2010 by Frederic Weisbecker <fweisbec@gmail.com>
#
# This software is distributed under the terms of the GNU General
# Public License ("GPL") version 2 as published by the Free Software
# Foundation.
try:
import wx
except ImportError:
raise ImportError("You need to install the wxpython lib for this script")
class RootFrame(wx.Frame):
Y_OFFSET = 100
RECT_HEIGHT = 100
RECT_SPACE = 50
EVENT_MARKING_WIDTH = 5
def __init__(self, sched_tracer, title, parent = None, id = -1):
wx.Frame.__init__(self, parent, id, title)
(self.screen_width, self.screen_height) = wx.GetDisplaySize()
self.screen_width -= 10
self.screen_height -= 10
self.zoom = 0.5
self.scroll_scale = 20
self.sched_tracer = sched_tracer
self.sched_tracer.set_root_win(self)
(self.ts_start, self.ts_end) = sched_tracer.interval()
self.update_width_virtual()
self.nr_rects = sched_tracer.nr_rectangles() + 1
self.height_virtual = RootFrame.Y_OFFSET + (self.nr_rects * (RootFrame.RECT_HEIGHT + RootFrame.RECT_SPACE))
# whole window panel
self.panel = wx.Panel(self, size=(self.screen_width, self.screen_height))
# scrollable container
self.scroll = wx.ScrolledWindow(self.panel)
self.scroll.SetScrollbars(self.scroll_scale, self.scroll_scale, self.width_virtual / self.scroll_scale, self.height_virtual / self.scroll_scale)
self.scroll.EnableScrolling(True, True)
self.scroll.SetFocus()
# scrollable drawing area
self.scroll_panel = wx.Panel(self.scroll, size=(self.screen_width - 15, self.screen_height / 2))
self.scroll_panel.Bind(wx.EVT_PAINT, self.on_paint)
self.scroll_panel.Bind(wx.EVT_KEY_DOWN, self.on_key_press)
self.scroll_panel.Bind(wx.EVT_LEFT_DOWN, self.on_mouse_down)
self.scroll.Bind(wx.EVT_PAINT, self.on_paint)
self.scroll.Bind(wx.EVT_KEY_DOWN, self.on_key_press)
self.scroll.Bind(wx.EVT_LEFT_DOWN, self.on_mouse_down)
self.scroll.Fit()
self.Fit()
self.scroll_panel.SetDimensions(-1, -1, self.width_virtual, self.height_virtual, wx.SIZE_USE_EXISTING)
self.txt = None
self.Show(True)
def us_to_px(self, val):
return val / (10 ** 3) * self.zoom
def px_to_us(self, val):
return (val / self.zoom) * (10 ** 3)
def scroll_start(self):
(x, y) = self.scroll.GetViewStart()
return (x * self.scroll_scale, y * self.scroll_scale)
def scroll_start_us(self):
(x, y) = self.scroll_start()
return self.px_to_us(x)
def paint_rectangle_zone(self, nr, color, top_color, start, end):
offset_px = self.us_to_px(start - self.ts_start)
width_px = self.us_to_px(end - self.ts_start)
offset_py = RootFrame.Y_OFFSET + (nr * (RootFrame.RECT_HEIGHT + RootFrame.RECT_SPACE))
width_py = RootFrame.RECT_HEIGHT
dc = self.dc
if top_color is not None:
(r, g, b) = top_color
top_color = wx.Colour(r, g, b)
brush = wx.Brush(top_color, wx.SOLID)
dc.SetBrush(brush)
dc.DrawRectangle(offset_px, offset_py, width_px, RootFrame.EVENT_MARKING_WIDTH)
width_py -= RootFrame.EVENT_MARKING_WIDTH
offset_py += RootFrame.EVENT_MARKING_WIDTH
(r ,g, b) = color
color = wx.Colour(r, g, b)
brush = wx.Brush(color, wx.SOLID)
dc.SetBrush(brush)
dc.DrawRectangle(offset_px, offset_py, width_px, width_py)
def update_rectangles(self, dc, start, end):
start += self.ts_start
end += self.ts_start
self.sched_tracer.fill_zone(start, end)
def on_paint(self, event):
dc = wx.PaintDC(self.scroll_panel)
self.dc = dc
width = min(self.width_virtual, self.screen_width)
(x, y) = self.scroll_start()
start = self.px_to_us(x)
end = self.px_to_us(x + width)
self.update_rectangles(dc, start, end)
def rect_from_ypixel(self, y):
y -= RootFrame.Y_OFFSET
rect = y / (RootFrame.RECT_HEIGHT + RootFrame.RECT_SPACE)
height = y % (RootFrame.RECT_HEIGHT + RootFrame.RECT_SPACE)
if rect < 0 or rect > self.nr_rects - 1 or height > RootFrame.RECT_HEIGHT:
return -1
return rect
def update_summary(self, txt):
if self.txt:
self.txt.Destroy()
self.txt = wx.StaticText(self.panel, -1, txt, (0, (self.screen_height / 2) + 50))
def on_mouse_down(self, event):
(x, y) = event.GetPositionTuple()
rect = self.rect_from_ypixel(y)
if rect == -1:
return
t = self.px_to_us(x) + self.ts_start
self.sched_tracer.mouse_down(rect, t)
def update_width_virtual(self):
self.width_virtual = self.us_to_px(self.ts_end - self.ts_start)
def __zoom(self, x):
self.update_width_virtual()
(xpos, ypos) = self.scroll.GetViewStart()
xpos = self.us_to_px(x) / self.scroll_scale
self.scroll.SetScrollbars(self.scroll_scale, self.scroll_scale, self.width_virtual / self.scroll_scale, self.height_virtual / self.scroll_scale, xpos, ypos)
self.Refresh()
def zoom_in(self):
x = self.scroll_start_us()
self.zoom *= 2
self.__zoom(x)
def zoom_out(self):
x = self.scroll_start_us()
self.zoom /= 2
self.__zoom(x)
def on_key_press(self, event):
key = event.GetRawKeyCode()
if key == ord("+"):
self.zoom_in()
return
if key == ord("-"):
self.zoom_out()
return
key = event.GetKeyCode()
(x, y) = self.scroll.GetViewStart()
if key == wx.WXK_RIGHT:
self.scroll.Scroll(x + 1, y)
elif key == wx.WXK_LEFT:
self.scroll.Scroll(x - 1, y)
elif key == wx.WXK_DOWN:
self.scroll.Scroll(x, y + 1)
elif key == wx.WXK_UP:
self.scroll.Scroll(x, y - 1)
@@ -0,0 +1,92 @@
# Util.py - Python extension for perf script, miscellaneous utility code
#
# Copyright (C) 2010 by Tom Zanussi <tzanussi@gmail.com>
#
# This software may be distributed under the terms of the GNU General
# Public License ("GPL") version 2 as published by the Free Software
# Foundation.
from __future__ import print_function
import errno, os
FUTEX_WAIT = 0
FUTEX_WAKE = 1
FUTEX_PRIVATE_FLAG = 128
FUTEX_CLOCK_REALTIME = 256
FUTEX_CMD_MASK = ~(FUTEX_PRIVATE_FLAG | FUTEX_CLOCK_REALTIME)
NSECS_PER_SEC = 1000000000
def avg(total, n):
return total / n
def nsecs(secs, nsecs):
return secs * NSECS_PER_SEC + nsecs
def nsecs_secs(nsecs):
return nsecs / NSECS_PER_SEC
def nsecs_nsecs(nsecs):
return nsecs % NSECS_PER_SEC
def nsecs_str(nsecs):
str = "%5u.%09u" % (nsecs_secs(nsecs), nsecs_nsecs(nsecs)),
return str
def add_stats(dict, key, value):
if key not in dict:
dict[key] = (value, value, value, 1)
else:
min, max, avg, count = dict[key]
if value < min:
min = value
if value > max:
max = value
avg = (avg + value) / 2
dict[key] = (min, max, avg, count + 1)
def clear_term():
print("\x1b[H\x1b[2J")
audit_package_warned = False
try:
import audit
machine_to_id = {
'x86_64': audit.MACH_86_64,
'aarch64': audit.MACH_AARCH64,
'alpha' : audit.MACH_ALPHA,
'ia64' : audit.MACH_IA64,
'ppc' : audit.MACH_PPC,
'ppc64' : audit.MACH_PPC64,
'ppc64le' : audit.MACH_PPC64LE,
's390' : audit.MACH_S390,
's390x' : audit.MACH_S390X,
'i386' : audit.MACH_X86,
'i586' : audit.MACH_X86,
'i686' : audit.MACH_X86,
}
try:
machine_to_id['armeb'] = audit.MACH_ARMEB
except:
pass
machine_id = machine_to_id[os.uname()[4]]
except:
if not audit_package_warned:
audit_package_warned = True
print("Install the python-audit package to get syscall names.\n"
"For example:\n # apt-get install python3-audit (Ubuntu)"
"\n # yum install python3-audit (Fedora)"
"\n etc.\n")
def syscall_name(id):
try:
return audit.audit_syscall_to_name(id, machine_id)
except:
return str(id)
def strerror(nr):
try:
return errno.errorcode[abs(nr)]
except:
return "Unknown %d errno" % nr
@@ -0,0 +1,355 @@
# SPDX-License-Identifier: GPL-2.0
# arm-cs-trace-disasm.py: ARM CoreSight Trace Dump With Disassember
#
# Author: Tor Jeremiassen <tor@ti.com>
# Mathieu Poirier <mathieu.poirier@linaro.org>
# Leo Yan <leo.yan@linaro.org>
# Al Grant <Al.Grant@arm.com>
from __future__ import print_function
import os
from os import path
import re
from subprocess import *
import argparse
import platform
from perf_trace_context import perf_sample_srccode, perf_config_get
# Below are some example commands for using this script.
# Note a --kcore recording is required for accurate decode
# due to the alternatives patching mechanism. In addition to this,
# source line info comes from Perf, and when using kcore there is
# no debug info. The following lists the supported features in each mode:
#
# +-----------+-----------------+------------------+------------------+
# | Recording | Accurate decode | Source line dump | Disassembly dump |
# +-----------+-----------------+------------------+------------------+
# | --kcore | yes | no | yes |
# | normal | no | yes (inaccurate) | yes (inaccurate) |
# +-----------+-----------------+------------------+------------------+
#
# Output disassembly with objdump and auto detect vmlinux
# (when running on same machine.):
# perf script -s scripts/python/arm-cs-trace-disasm.py -d
#
# Output disassembly with llvm-objdump:
# perf script -s scripts/python/arm-cs-trace-disasm.py \
# -- -d llvm-objdump-11 -k path/to/vmlinux
#
# Output accurate disassembly by passing kcore to script:
# perf script -s scripts/python/arm-cs-trace-disasm.py \
# -- -d -k perf.data/kcore_dir/kcore
#
# Output only source line and symbols:
# perf script -s scripts/python/arm-cs-trace-disasm.py
def default_objdump():
config = perf_config_get("annotate.objdump")
return config if config else "objdump"
# Command line parsing.
def int_arg(v):
v = int(v)
if v < 0:
raise argparse.ArgumentTypeError("Argument must be a positive integer")
return v
args = argparse.ArgumentParser()
args.add_argument("-k", "--vmlinux",
help="Set path to vmlinux or kcore file. Omit to autodetect if running on same machine")
args.add_argument("-d", "--objdump", nargs="?", const=default_objdump(),
help="Show disassembly. Can also be used to change the objdump path"),
args.add_argument("-v", "--verbose", action="store_true", help="Enable debugging log")
args.add_argument("--start-time", type=int_arg, help="Monotonic clock time of sample to start from. "
"See 'time' field on samples in -v mode.")
args.add_argument("--stop-time", type=int_arg, help="Monotonic clock time of sample to stop at. "
"See 'time' field on samples in -v mode.")
args.add_argument("--start-sample", type=int_arg, help="Index of sample to start from. "
"See 'index' field on samples in -v mode.")
args.add_argument("--stop-sample", type=int_arg, help="Index of sample to stop at. "
"See 'index' field on samples in -v mode.")
options = args.parse_args()
if (options.start_time and options.stop_time and
options.start_time >= options.stop_time):
print("--start-time must less than --stop-time")
exit(2)
if (options.start_sample and options.stop_sample and
options.start_sample >= options.stop_sample):
print("--start-sample must less than --stop-sample")
exit(2)
# Initialize global dicts and regular expression
disasm_cache = dict()
cpu_data = dict()
disasm_re = re.compile(r"^\s*([0-9a-fA-F]+):")
disasm_func_re = re.compile(r"^\s*([0-9a-fA-F]+)\s.*:")
cache_size = 64*1024
sample_idx = -1
glb_source_file_name = None
glb_line_number = None
glb_dso = None
kver = platform.release()
vmlinux_paths = [
f"/usr/lib/debug/boot/vmlinux-{kver}.debug",
f"/usr/lib/debug/lib/modules/{kver}/vmlinux",
f"/lib/modules/{kver}/build/vmlinux",
f"/usr/lib/debug/boot/vmlinux-{kver}",
f"/boot/vmlinux-{kver}",
f"/boot/vmlinux",
f"vmlinux"
]
def get_optional(perf_dict, field):
if field in perf_dict:
return perf_dict[field]
return "[unknown]"
def get_offset(perf_dict, field):
if field in perf_dict:
return "+%#x" % perf_dict[field]
return ""
def find_vmlinux():
if hasattr(find_vmlinux, "path"):
return find_vmlinux.path
for v in vmlinux_paths:
if os.access(v, os.R_OK):
find_vmlinux.path = v
break
else:
find_vmlinux.path = None
return find_vmlinux.path
def get_dso_file_path(dso_name, dso_build_id):
if (dso_name == "[kernel.kallsyms]" or dso_name == "vmlinux"):
if (options.vmlinux):
return options.vmlinux;
else:
return find_vmlinux() if find_vmlinux() else dso_name
if (dso_name == "[vdso]") :
append = "/vdso"
else:
append = "/elf"
dso_path = os.environ['PERF_BUILDID_DIR'] + "/" + dso_name + "/" + dso_build_id + append;
# Replace duplicate slash chars to single slash char
dso_path = dso_path.replace('//', '/', 1)
return dso_path
def read_disam(dso_fname, dso_start, start_addr, stop_addr):
addr_range = str(start_addr) + ":" + str(stop_addr) + ":" + dso_fname
# Don't let the cache get too big, clear it when it hits max size
if (len(disasm_cache) > cache_size):
disasm_cache.clear();
if addr_range in disasm_cache:
disasm_output = disasm_cache[addr_range];
else:
start_addr = start_addr - dso_start;
stop_addr = stop_addr - dso_start;
disasm = [ options.objdump, "-d", "-z",
"--start-address="+format(start_addr,"#x"),
"--stop-address="+format(stop_addr,"#x") ]
disasm += [ dso_fname ]
disasm_output = check_output(disasm).decode('utf-8').split('\n')
disasm_cache[addr_range] = disasm_output
return disasm_output
def print_disam(dso_fname, dso_start, start_addr, stop_addr):
for line in read_disam(dso_fname, dso_start, start_addr, stop_addr):
m = disasm_func_re.search(line)
if m is None:
m = disasm_re.search(line)
if m is None:
continue
print("\t" + line)
def print_sample(sample):
print("Sample = { cpu: %04d addr: 0x%016x phys_addr: 0x%016x ip: 0x%016x " \
"pid: %d tid: %d period: %d time: %d index: %d}" % \
(sample['cpu'], sample['addr'], sample['phys_addr'], \
sample['ip'], sample['pid'], sample['tid'], \
sample['period'], sample['time'], sample_idx))
def trace_begin():
print('ARM CoreSight Trace Data Assembler Dump')
def trace_end():
print('End')
def trace_unhandled(event_name, context, event_fields_dict):
print(' '.join(['%s=%s'%(k,str(v))for k,v in sorted(event_fields_dict.items())]))
def common_start_str(comm, sample):
sec = int(sample["time"] / 1000000000)
ns = sample["time"] % 1000000000
cpu = sample["cpu"]
pid = sample["pid"]
tid = sample["tid"]
return "%16s %5u/%-5u [%04u] %9u.%09u " % (comm, pid, tid, cpu, sec, ns)
# This code is copied from intel-pt-events.py for printing source code
# line and symbols.
def print_srccode(comm, param_dict, sample, symbol, dso):
ip = sample["ip"]
if symbol == "[unknown]":
start_str = common_start_str(comm, sample) + ("%x" % ip).rjust(16).ljust(40)
else:
offs = get_offset(param_dict, "symoff")
start_str = common_start_str(comm, sample) + (symbol + offs).ljust(40)
global glb_source_file_name
global glb_line_number
global glb_dso
source_file_name, line_number, source_line = perf_sample_srccode(perf_script_context)
if source_file_name:
if glb_line_number == line_number and glb_source_file_name == source_file_name:
src_str = ""
else:
if len(source_file_name) > 40:
src_file = ("..." + source_file_name[-37:]) + " "
else:
src_file = source_file_name.ljust(41)
if source_line is None:
src_str = src_file + str(line_number).rjust(4) + " <source not found>"
else:
src_str = src_file + str(line_number).rjust(4) + " " + source_line
glb_dso = None
elif dso == glb_dso:
src_str = ""
else:
src_str = dso
glb_dso = dso
glb_line_number = line_number
glb_source_file_name = source_file_name
print(start_str, src_str)
def process_event(param_dict):
global cache_size
global options
global sample_idx
sample = param_dict["sample"]
comm = param_dict["comm"]
name = param_dict["ev_name"]
dso = get_optional(param_dict, "dso")
dso_bid = get_optional(param_dict, "dso_bid")
dso_start = get_optional(param_dict, "dso_map_start")
dso_end = get_optional(param_dict, "dso_map_end")
symbol = get_optional(param_dict, "symbol")
map_pgoff = get_optional(param_dict, "map_pgoff")
# check for valid map offset
if (str(map_pgoff) == '[unknown]'):
map_pgoff = 0
cpu = sample["cpu"]
ip = sample["ip"]
addr = sample["addr"]
sample_idx += 1
if (options.start_time and sample["time"] < options.start_time):
return
if (options.stop_time and sample["time"] > options.stop_time):
exit(0)
if (options.start_sample and sample_idx < options.start_sample):
return
if (options.stop_sample and sample_idx > options.stop_sample):
exit(0)
if (options.verbose == True):
print("Event type: %s" % name)
print_sample(sample)
# Initialize CPU data if it's empty, and directly return back
# if this is the first tracing event for this CPU.
if (cpu_data.get(str(cpu) + 'addr') == None):
cpu_data[str(cpu) + 'addr'] = addr
return
# If cannot find dso so cannot dump assembler, bail out
if (dso == '[unknown]'):
return
# Validate dso start and end addresses
if ((dso_start == '[unknown]') or (dso_end == '[unknown]')):
print("Failed to find valid dso map for dso %s" % dso)
return
if (name[0:12] == "instructions"):
print_srccode(comm, param_dict, sample, symbol, dso)
return
# Don't proceed if this event is not a branch sample, .
if (name[0:8] != "branches"):
return
# The format for packet is:
#
# +------------+------------+------------+
# sample_prev: | addr | ip | cpu |
# +------------+------------+------------+
# sample_next: | addr | ip | cpu |
# +------------+------------+------------+
#
# We need to combine the two continuous packets to get the instruction
# range for sample_prev::cpu:
#
# [ sample_prev::addr .. sample_next::ip ]
#
# For this purose, sample_prev::addr is stored into cpu_data structure
# and read back for 'start_addr' when the new packet comes, and we need
# to use sample_next::ip to calculate 'stop_addr', plusing extra 4 for
# 'stop_addr' is for the sake of objdump so the final assembler dump can
# include last instruction for sample_next::ip.
start_addr = cpu_data[str(cpu) + 'addr']
stop_addr = ip + 4
# Record for previous sample packet
cpu_data[str(cpu) + 'addr'] = addr
# Filter out zero start_address. Optionally identify CS_ETM_TRACE_ON packet
if (start_addr == 0):
if ((stop_addr == 4) and (options.verbose == True)):
print("CPU%d: CS_ETM_TRACE_ON packet is inserted" % cpu)
return
if (start_addr < int(dso_start) or start_addr > int(dso_end)):
print("Start address 0x%x is out of range [ 0x%x .. 0x%x ] for dso %s" % (start_addr, int(dso_start), int(dso_end), dso))
return
if (stop_addr < int(dso_start) or stop_addr > int(dso_end)):
print("Stop address 0x%x is out of range [ 0x%x .. 0x%x ] for dso %s" % (stop_addr, int(dso_start), int(dso_end), dso))
return
if (options.objdump != None):
# It doesn't need to decrease virtual memory offset for disassembly
# for kernel dso and executable file dso, so in this case we set
# vm_start to zero.
if (dso == "[kernel.kallsyms]" or dso_start == 0x400000):
dso_vm_start = 0
map_pgoff = 0
else:
dso_vm_start = int(dso_start)
dso_fname = get_dso_file_path(dso, dso_bid)
if path.exists(dso_fname):
print_disam(dso_fname, dso_vm_start, start_addr + map_pgoff, stop_addr + map_pgoff)
else:
print("Failed to find dso %s for address range [ 0x%x .. 0x%x ]" % (dso, start_addr + map_pgoff, stop_addr + map_pgoff))
print_srccode(comm, param_dict, sample, symbol, dso)
@@ -0,0 +1,2 @@
#!/bin/bash
perf record -e compaction:mm_compaction_begin -e compaction:mm_compaction_end -e compaction:mm_compaction_migratepages -e compaction:mm_compaction_isolate_migratepages -e compaction:mm_compaction_isolate_freepages $@
@@ -0,0 +1,4 @@
#!/bin/bash
#description: display time taken by mm compaction
#args: [-h] [-u] [-p|-pv] [-t | [-m] [-fs] [-ms]] [pid|pid-range|comm-regex]
perf script -s "$PERF_EXEC_PATH"/scripts/python/compaction-times.py $@
@@ -0,0 +1,8 @@
#!/bin/bash
#
# event_analyzing_sample.py can cover all type of perf samples including
# the tracepoints, so no special record requirements, just record what
# you want to analyze.
#
perf record $@
@@ -0,0 +1,3 @@
#!/bin/bash
# description: analyze all perf samples
perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/event_analyzing_sample.py
@@ -0,0 +1,8 @@
#!/bin/bash
#
# export perf data to a postgresql database. Can cover
# perf ip samples (excluding the tracepoints). No special
# record requirements, just record what you want to export.
#
perf record $@
@@ -0,0 +1,29 @@
#!/bin/bash
# description: export perf data to a postgresql database
# args: [database name] [columns] [calls]
n_args=0
for i in "$@"
do
if expr match "$i" "-" > /dev/null ; then
break
fi
n_args=$(( $n_args + 1 ))
done
if [ "$n_args" -gt 3 ] ; then
echo "usage: export-to-postgresql-report [database name] [columns] [calls]"
exit
fi
if [ "$n_args" -gt 2 ] ; then
dbname=$1
columns=$2
calls=$3
shift 3
elif [ "$n_args" -gt 1 ] ; then
dbname=$1
columns=$2
shift 2
elif [ "$n_args" -gt 0 ] ; then
dbname=$1
shift
fi
perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/export-to-postgresql.py $dbname $columns $calls
@@ -0,0 +1,8 @@
#!/bin/bash
#
# export perf data to a sqlite3 database. Can cover
# perf ip samples (excluding the tracepoints). No special
# record requirements, just record what you want to export.
#
perf record $@
@@ -0,0 +1,29 @@
#!/bin/bash
# description: export perf data to a sqlite3 database
# args: [database name] [columns] [calls]
n_args=0
for i in "$@"
do
if expr match "$i" "-" > /dev/null ; then
break
fi
n_args=$(( $n_args + 1 ))
done
if [ "$n_args" -gt 3 ] ; then
echo "usage: export-to-sqlite-report [database name] [columns] [calls]"
exit
fi
if [ "$n_args" -gt 2 ] ; then
dbname=$1
columns=$2
calls=$3
shift 3
elif [ "$n_args" -gt 1 ] ; then
dbname=$1
columns=$2
shift 2
elif [ "$n_args" -gt 0 ] ; then
dbname=$1
shift
fi
perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/export-to-sqlite.py $dbname $columns $calls
@@ -0,0 +1,3 @@
#!/bin/bash
(perf record -e raw_syscalls:sys_exit $@ || \
perf record -e syscalls:sys_exit $@) 2> /dev/null
@@ -0,0 +1,10 @@
#!/bin/bash
# description: system-wide failed syscalls, by pid
# args: [comm]
if [ $# -gt 0 ] ; then
if ! expr match "$1" "-" > /dev/null ; then
comm=$1
shift
fi
fi
perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/failed-syscalls-by-pid.py $comm
@@ -0,0 +1,2 @@
#!/bin/bash
perf record -g "$@"
@@ -0,0 +1,3 @@
#!/bin/bash
# description: create flame graphs
perf script -s "$PERF_EXEC_PATH"/scripts/python/flamegraph.py "$@"
@@ -0,0 +1,2 @@
#!/bin/bash
perf record -e syscalls:sys_enter_futex -e syscalls:sys_exit_futex $@
@@ -0,0 +1,4 @@
#!/bin/bash
# description: futext contention measurement
perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/futex-contention.py
@@ -0,0 +1,2 @@
#!/bin/bash
perf record -F 99 -g "$@"
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
# description: create firefox gecko profile json format from perf.data
if [ "$*" = "-i -" ]; then
perf script -s "$PERF_EXEC_PATH"/scripts/python/gecko.py
else
perf script -s "$PERF_EXEC_PATH"/scripts/python/gecko.py -- "$@"
fi
@@ -0,0 +1,13 @@
#!/bin/bash
#
# print Intel PT Events including Power Events and PTWRITE. The intel_pt PMU
# event needs to be specified with appropriate config terms.
#
if ! echo "$@" | grep -q intel_pt ; then
echo "Options must include the Intel PT event e.g. -e intel_pt/pwr_evt,ptw/"
echo "and for power events it probably needs to be system wide i.e. -a option"
echo "For example: -a -e intel_pt/pwr_evt,branch=0/ sleep 1"
exit 1
fi
perf record $@
@@ -0,0 +1,3 @@
#!/bin/bash
# description: print Intel PT Events including Power Events and PTWRITE
perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/intel-pt-events.py
@@ -0,0 +1,19 @@
#!/bin/bash
#
# Profiling physical memory by all retired load instructions/uops event
# MEM_INST_RETIRED.ALL_LOADS or MEM_UOPS_RETIRED.ALL_LOADS
#
load=`perf list | grep mem_inst_retired.all_loads`
if [ -z "$load" ]; then
load=`perf list | grep mem_uops_retired.all_loads`
fi
if [ -z "$load" ]; then
echo "There is no event to count all retired load instructions/uops."
exit 1
fi
arg=$(echo $load | tr -d ' ')
arg="$arg:P"
perf record --phys-data -e $arg $@
@@ -0,0 +1,3 @@
#!/bin/bash
# description: resolve physical address samples
perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/mem-phys-addr.py
@@ -0,0 +1,2 @@
#!/bin/bash
perf record -e skb:kfree_skb $@
@@ -0,0 +1,4 @@
#!/bin/bash
# description: display a table of dropped frames
perf script -s "$PERF_EXEC_PATH"/scripts/python/net_dropmonitor.py $@
@@ -0,0 +1,8 @@
#!/bin/bash
perf record -e net:net_dev_xmit -e net:net_dev_queue \
-e net:netif_receive_skb -e net:netif_rx \
-e skb:consume_skb -e skb:kfree_skb \
-e skb:skb_copy_datagram_iovec -e napi:napi_poll \
-e irq:irq_handler_entry -e irq:irq_handler_exit \
-e irq:softirq_entry -e irq:softirq_exit \
-e irq:softirq_raise $@
@@ -0,0 +1,5 @@
#!/bin/bash
# description: display a process of packet and processing time
# args: [tx] [rx] [dev=] [debug]
perf script -s "$PERF_EXEC_PATH"/scripts/python/netdev-times.py $@
@@ -0,0 +1,2 @@
#!/bin/bash
perf record -e "{powerpc:hcall_entry,powerpc:hcall_exit}" $@
@@ -0,0 +1,2 @@
#!/bin/bash
perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/powerpc-hcalls.py
@@ -0,0 +1,2 @@
#!/bin/bash
perf record -m 16384 -e sched:sched_wakeup -e sched:sched_wakeup_new -e sched:sched_switch -e sched:sched_migrate_task $@
@@ -0,0 +1,3 @@
#!/bin/bash
# description: sched migration overview
perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/sched-migration.py
@@ -0,0 +1,3 @@
#!/bin/bash
(perf record -e raw_syscalls:sys_enter $@ || \
perf record -e syscalls:sys_enter $@) 2> /dev/null
@@ -0,0 +1,24 @@
#!/bin/bash
# description: syscall top
# args: [comm] [interval]
n_args=0
for i in "$@"
do
if expr match "$i" "-" > /dev/null ; then
break
fi
n_args=$(( $n_args + 1 ))
done
if [ "$n_args" -gt 2 ] ; then
echo "usage: sctop-report [comm] [interval]"
exit
fi
if [ "$n_args" -gt 1 ] ; then
comm=$1
interval=$2
shift 2
elif [ "$n_args" -gt 0 ] ; then
interval=$1
shift
fi
perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/sctop.py $comm $interval
@@ -0,0 +1,8 @@
#!/bin/sh
#
# stackcollapse.py can cover all type of perf samples including
# the tracepoints, so no special record requirements, just record what
# you want to analyze.
#
perf record "$@"
@@ -0,0 +1,3 @@
#!/bin/sh
# description: produce callgraphs in short form for scripting use
perf script -s "$PERF_EXEC_PATH"/scripts/python/stackcollapse.py "$@"
@@ -0,0 +1,3 @@
#!/bin/bash
(perf record -e raw_syscalls:sys_enter $@ || \
perf record -e syscalls:sys_enter $@) 2> /dev/null
@@ -0,0 +1,10 @@
#!/bin/bash
# description: system-wide syscall counts, by pid
# args: [comm]
if [ $# -gt 0 ] ; then
if ! expr match "$1" "-" > /dev/null ; then
comm=$1
shift
fi
fi
perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/syscall-counts-by-pid.py $comm
@@ -0,0 +1,3 @@
#!/bin/bash
(perf record -e raw_syscalls:sys_enter $@ || \
perf record -e syscalls:sys_enter $@) 2> /dev/null
@@ -0,0 +1,10 @@
#!/bin/bash
# description: system-wide syscall counts
# args: [comm]
if [ $# -gt 0 ] ; then
if ! expr match "$1" "-" > /dev/null ; then
comm=$1
shift
fi
fi
perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/syscall-counts.py $comm
@@ -0,0 +1,2 @@
#!/bin/bash
perf record -e sched:sched_switch -e sched:sched_migrate_task "$@"
@@ -0,0 +1,3 @@
#!/bin/bash
# description: analyze timings of tasks
perf script -s "$PERF_EXEC_PATH"/scripts/python/task-analyzer.py -- "$@"
@@ -0,0 +1,84 @@
# perf script event handlers, generated by perf script -g python
# (c) 2010, Tom Zanussi <tzanussi@gmail.com>
# Licensed under the terms of the GNU GPL License version 2
#
# This script tests basic functionality such as flag and symbol
# strings, common_xxx() calls back into perf, begin, end, unhandled
# events, etc. Basically, if this script runs successfully and
# displays expected results, Python scripting support should be ok.
from __future__ import print_function
import os
import sys
sys.path.append(os.environ['PERF_EXEC_PATH'] + \
'/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
from Core import *
from perf_trace_context import *
unhandled = autodict()
def trace_begin():
print("trace_begin")
pass
def trace_end():
print_unhandled()
def irq__softirq_entry(event_name, context, common_cpu,
common_secs, common_nsecs, common_pid, common_comm,
common_callchain, vec):
print_header(event_name, common_cpu, common_secs, common_nsecs,
common_pid, common_comm)
print_uncommon(context)
print("vec=%s" % (symbol_str("irq__softirq_entry", "vec", vec)))
def kmem__kmalloc(event_name, context, common_cpu,
common_secs, common_nsecs, common_pid, common_comm,
common_callchain, call_site, ptr, bytes_req, bytes_alloc,
gfp_flags):
print_header(event_name, common_cpu, common_secs, common_nsecs,
common_pid, common_comm)
print_uncommon(context)
print("call_site=%u, ptr=%u, bytes_req=%u, "
"bytes_alloc=%u, gfp_flags=%s" %
(call_site, ptr, bytes_req, bytes_alloc,
flag_str("kmem__kmalloc", "gfp_flags", gfp_flags)))
def trace_unhandled(event_name, context, event_fields_dict):
try:
unhandled[event_name] += 1
except TypeError:
unhandled[event_name] = 1
def print_header(event_name, cpu, secs, nsecs, pid, comm):
print("%-20s %5u %05u.%09u %8u %-20s " %
(event_name, cpu, secs, nsecs, pid, comm),
end=' ')
# print trace fields not included in handler args
def print_uncommon(context):
print("common_preempt_count=%d, common_flags=%s, "
"common_lock_depth=%d, " %
(common_pc(context), trace_flag_str(common_flags(context)),
common_lock_depth(context)))
def print_unhandled():
keys = unhandled.keys()
if not keys:
return
print("\nunhandled events:\n")
print("%-40s %10s" % ("event", "count"))
print("%-40s %10s" % ("----------------------------------------",
"-----------"))
for event_name in keys:
print("%-40s %10d\n" % (event_name, unhandled[event_name]))
@@ -0,0 +1,311 @@
# report time spent in compaction
# Licensed under the terms of the GNU GPL License version 2
# testing:
# 'echo 1 > /proc/sys/vm/compact_memory' to force compaction of all zones
import os
import sys
import re
import signal
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
usage = "usage: perf script report compaction-times.py -- [-h] [-u] [-p|-pv] [-t | [-m] [-fs] [-ms]] [pid|pid-range|comm-regex]\n"
class popt:
DISP_DFL = 0
DISP_PROC = 1
DISP_PROC_VERBOSE=2
class topt:
DISP_TIME = 0
DISP_MIG = 1
DISP_ISOLFREE = 2
DISP_ISOLMIG = 4
DISP_ALL = 7
class comm_filter:
def __init__(self, re):
self.re = re
def filter(self, pid, comm):
m = self.re.search(comm)
return m == None or m.group() == ""
class pid_filter:
def __init__(self, low, high):
self.low = (0 if low == "" else int(low))
self.high = (0 if high == "" else int(high))
def filter(self, pid, comm):
return not (pid >= self.low and (self.high == 0 or pid <= self.high))
def set_type(t):
global opt_disp
opt_disp = (t if opt_disp == topt.DISP_ALL else opt_disp|t)
def ns(sec, nsec):
return (sec * 1000000000) + nsec
def time(ns):
return "%dns" % ns if opt_ns else "%dus" % (round(ns, -3) / 1000)
class pair:
def __init__(self, aval, bval, alabel = None, blabel = None):
self.alabel = alabel
self.blabel = blabel
self.aval = aval
self.bval = bval
def __add__(self, rhs):
self.aval += rhs.aval
self.bval += rhs.bval
return self
def __str__(self):
return "%s=%d %s=%d" % (self.alabel, self.aval, self.blabel, self.bval)
class cnode:
def __init__(self, ns):
self.ns = ns
self.migrated = pair(0, 0, "moved", "failed")
self.fscan = pair(0,0, "scanned", "isolated")
self.mscan = pair(0,0, "scanned", "isolated")
def __add__(self, rhs):
self.ns += rhs.ns
self.migrated += rhs.migrated
self.fscan += rhs.fscan
self.mscan += rhs.mscan
return self
def __str__(self):
prev = 0
s = "%s " % time(self.ns)
if (opt_disp & topt.DISP_MIG):
s += "migration: %s" % self.migrated
prev = 1
if (opt_disp & topt.DISP_ISOLFREE):
s += "%sfree_scanner: %s" % (" " if prev else "", self.fscan)
prev = 1
if (opt_disp & topt.DISP_ISOLMIG):
s += "%smigration_scanner: %s" % (" " if prev else "", self.mscan)
return s
def complete(self, secs, nsecs):
self.ns = ns(secs, nsecs) - self.ns
def increment(self, migrated, fscan, mscan):
if (migrated != None):
self.migrated += migrated
if (fscan != None):
self.fscan += fscan
if (mscan != None):
self.mscan += mscan
class chead:
heads = {}
val = cnode(0);
fobj = None
@classmethod
def add_filter(cls, filter):
cls.fobj = filter
@classmethod
def create_pending(cls, pid, comm, start_secs, start_nsecs):
filtered = 0
try:
head = cls.heads[pid]
filtered = head.is_filtered()
except KeyError:
if cls.fobj != None:
filtered = cls.fobj.filter(pid, comm)
head = cls.heads[pid] = chead(comm, pid, filtered)
if not filtered:
head.mark_pending(start_secs, start_nsecs)
@classmethod
def increment_pending(cls, pid, migrated, fscan, mscan):
head = cls.heads[pid]
if not head.is_filtered():
if head.is_pending():
head.do_increment(migrated, fscan, mscan)
else:
sys.stderr.write("missing start compaction event for pid %d\n" % pid)
@classmethod
def complete_pending(cls, pid, secs, nsecs):
head = cls.heads[pid]
if not head.is_filtered():
if head.is_pending():
head.make_complete(secs, nsecs)
else:
sys.stderr.write("missing start compaction event for pid %d\n" % pid)
@classmethod
def gen(cls):
if opt_proc != popt.DISP_DFL:
for i in cls.heads:
yield cls.heads[i]
@classmethod
def str(cls):
return cls.val
def __init__(self, comm, pid, filtered):
self.comm = comm
self.pid = pid
self.val = cnode(0)
self.pending = None
self.filtered = filtered
self.list = []
def __add__(self, rhs):
self.ns += rhs.ns
self.val += rhs.val
return self
def mark_pending(self, secs, nsecs):
self.pending = cnode(ns(secs, nsecs))
def do_increment(self, migrated, fscan, mscan):
self.pending.increment(migrated, fscan, mscan)
def make_complete(self, secs, nsecs):
self.pending.complete(secs, nsecs)
chead.val += self.pending
if opt_proc != popt.DISP_DFL:
self.val += self.pending
if opt_proc == popt.DISP_PROC_VERBOSE:
self.list.append(self.pending)
self.pending = None
def enumerate(self):
if opt_proc == popt.DISP_PROC_VERBOSE and not self.is_filtered():
for i, pelem in enumerate(self.list):
sys.stdout.write("%d[%s].%d: %s\n" % (self.pid, self.comm, i+1, pelem))
def is_pending(self):
return self.pending != None
def is_filtered(self):
return self.filtered
def display(self):
if not self.is_filtered():
sys.stdout.write("%d[%s]: %s\n" % (self.pid, self.comm, self.val))
def trace_end():
sys.stdout.write("total: %s\n" % chead.str())
for i in chead.gen():
i.display(),
i.enumerate()
def compaction__mm_compaction_migratepages(event_name, context, common_cpu,
common_secs, common_nsecs, common_pid, common_comm,
common_callchain, nr_migrated, nr_failed):
chead.increment_pending(common_pid,
pair(nr_migrated, nr_failed), None, None)
def compaction__mm_compaction_isolate_freepages(event_name, context, common_cpu,
common_secs, common_nsecs, common_pid, common_comm,
common_callchain, start_pfn, end_pfn, nr_scanned, nr_taken):
chead.increment_pending(common_pid,
None, pair(nr_scanned, nr_taken), None)
def compaction__mm_compaction_isolate_migratepages(event_name, context, common_cpu,
common_secs, common_nsecs, common_pid, common_comm,
common_callchain, start_pfn, end_pfn, nr_scanned, nr_taken):
chead.increment_pending(common_pid,
None, None, pair(nr_scanned, nr_taken))
def compaction__mm_compaction_end(event_name, context, common_cpu,
common_secs, common_nsecs, common_pid, common_comm,
common_callchain, zone_start, migrate_start, free_start, zone_end,
sync, status):
chead.complete_pending(common_pid, common_secs, common_nsecs)
def compaction__mm_compaction_begin(event_name, context, common_cpu,
common_secs, common_nsecs, common_pid, common_comm,
common_callchain, zone_start, migrate_start, free_start, zone_end,
sync):
chead.create_pending(common_pid, common_comm, common_secs, common_nsecs)
def pr_help():
global usage
sys.stdout.write(usage)
sys.stdout.write("\n")
sys.stdout.write("-h display this help\n")
sys.stdout.write("-p display by process\n")
sys.stdout.write("-pv display by process (verbose)\n")
sys.stdout.write("-t display stall times only\n")
sys.stdout.write("-m display stats for migration\n")
sys.stdout.write("-fs display stats for free scanner\n")
sys.stdout.write("-ms display stats for migration scanner\n")
sys.stdout.write("-u display results in microseconds (default nanoseconds)\n")
comm_re = None
pid_re = None
pid_regex = r"^(\d*)-(\d*)$|^(\d*)$"
opt_proc = popt.DISP_DFL
opt_disp = topt.DISP_ALL
opt_ns = True
argc = len(sys.argv) - 1
if argc >= 1:
pid_re = re.compile(pid_regex)
for i, opt in enumerate(sys.argv[1:]):
if opt[0] == "-":
if opt == "-h":
pr_help()
exit(0);
elif opt == "-p":
opt_proc = popt.DISP_PROC
elif opt == "-pv":
opt_proc = popt.DISP_PROC_VERBOSE
elif opt == '-u':
opt_ns = False
elif opt == "-t":
set_type(topt.DISP_TIME)
elif opt == "-m":
set_type(topt.DISP_MIG)
elif opt == "-fs":
set_type(topt.DISP_ISOLFREE)
elif opt == "-ms":
set_type(topt.DISP_ISOLMIG)
else:
sys.exit(usage)
elif i == argc - 1:
m = pid_re.search(opt)
if m != None and m.group() != "":
if m.group(3) != None:
f = pid_filter(m.group(3), m.group(3))
else:
f = pid_filter(m.group(1), m.group(2))
else:
try:
comm_re=re.compile(opt)
except:
sys.stderr.write("invalid regex '%s'" % opt)
sys.exit(usage)
f = comm_filter(comm_re)
chead.add_filter(f)
@@ -0,0 +1,192 @@
# event_analyzing_sample.py: general event handler in python
# SPDX-License-Identifier: GPL-2.0
#
# Current perf report is already very powerful with the annotation integrated,
# and this script is not trying to be as powerful as perf report, but
# providing end user/developer a flexible way to analyze the events other
# than trace points.
#
# The 2 database related functions in this script just show how to gather
# the basic information, and users can modify and write their own functions
# according to their specific requirement.
#
# The first function "show_general_events" just does a basic grouping for all
# generic events with the help of sqlite, and the 2nd one "show_pebs_ll" is
# for a x86 HW PMU event: PEBS with load latency data.
#
from __future__ import print_function
import os
import sys
import math
import struct
import sqlite3
sys.path.append(os.environ['PERF_EXEC_PATH'] + \
'/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
from perf_trace_context import *
from EventClass import *
#
# If the perf.data has a big number of samples, then the insert operation
# will be very time consuming (about 10+ minutes for 10000 samples) if the
# .db database is on disk. Move the .db file to RAM based FS to speedup
# the handling, which will cut the time down to several seconds.
#
con = sqlite3.connect("/dev/shm/perf.db")
con.isolation_level = None
def trace_begin():
print("In trace_begin:\n")
#
# Will create several tables at the start, pebs_ll is for PEBS data with
# load latency info, while gen_events is for general event.
#
con.execute("""
create table if not exists gen_events (
name text,
symbol text,
comm text,
dso text
);""")
con.execute("""
create table if not exists pebs_ll (
name text,
symbol text,
comm text,
dso text,
flags integer,
ip integer,
status integer,
dse integer,
dla integer,
lat integer
);""")
#
# Create and insert event object to a database so that user could
# do more analysis with simple database commands.
#
def process_event(param_dict):
event_attr = param_dict["attr"]
sample = param_dict["sample"]
raw_buf = param_dict["raw_buf"]
comm = param_dict["comm"]
name = param_dict["ev_name"]
# Symbol and dso info are not always resolved
if ("dso" in param_dict):
dso = param_dict["dso"]
else:
dso = "Unknown_dso"
if ("symbol" in param_dict):
symbol = param_dict["symbol"]
else:
symbol = "Unknown_symbol"
# Create the event object and insert it to the right table in database
event = create_event(name, comm, dso, symbol, raw_buf)
insert_db(event)
def insert_db(event):
if event.ev_type == EVTYPE_GENERIC:
con.execute("insert into gen_events values(?, ?, ?, ?)",
(event.name, event.symbol, event.comm, event.dso))
elif event.ev_type == EVTYPE_PEBS_LL:
event.ip &= 0x7fffffffffffffff
event.dla &= 0x7fffffffffffffff
con.execute("insert into pebs_ll values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
(event.name, event.symbol, event.comm, event.dso, event.flags,
event.ip, event.status, event.dse, event.dla, event.lat))
def trace_end():
print("In trace_end:\n")
# We show the basic info for the 2 type of event classes
show_general_events()
show_pebs_ll()
con.close()
#
# As the event number may be very big, so we can't use linear way
# to show the histogram in real number, but use a log2 algorithm.
#
def num2sym(num):
# Each number will have at least one '#'
snum = '#' * (int)(math.log(num, 2) + 1)
return snum
def show_general_events():
# Check the total record number in the table
count = con.execute("select count(*) from gen_events")
for t in count:
print("There is %d records in gen_events table" % t[0])
if t[0] == 0:
return
print("Statistics about the general events grouped by thread/symbol/dso: \n")
# Group by thread
commq = con.execute("select comm, count(comm) from gen_events group by comm order by -count(comm)")
print("\n%16s %8s %16s\n%s" % ("comm", "number", "histogram", "="*42))
for row in commq:
print("%16s %8d %s" % (row[0], row[1], num2sym(row[1])))
# Group by symbol
print("\n%32s %8s %16s\n%s" % ("symbol", "number", "histogram", "="*58))
symbolq = con.execute("select symbol, count(symbol) from gen_events group by symbol order by -count(symbol)")
for row in symbolq:
print("%32s %8d %s" % (row[0], row[1], num2sym(row[1])))
# Group by dso
print("\n%40s %8s %16s\n%s" % ("dso", "number", "histogram", "="*74))
dsoq = con.execute("select dso, count(dso) from gen_events group by dso order by -count(dso)")
for row in dsoq:
print("%40s %8d %s" % (row[0], row[1], num2sym(row[1])))
#
# This function just shows the basic info, and we could do more with the
# data in the tables, like checking the function parameters when some
# big latency events happen.
#
def show_pebs_ll():
count = con.execute("select count(*) from pebs_ll")
for t in count:
print("There is %d records in pebs_ll table" % t[0])
if t[0] == 0:
return
print("Statistics about the PEBS Load Latency events grouped by thread/symbol/dse/latency: \n")
# Group by thread
commq = con.execute("select comm, count(comm) from pebs_ll group by comm order by -count(comm)")
print("\n%16s %8s %16s\n%s" % ("comm", "number", "histogram", "="*42))
for row in commq:
print("%16s %8d %s" % (row[0], row[1], num2sym(row[1])))
# Group by symbol
print("\n%32s %8s %16s\n%s" % ("symbol", "number", "histogram", "="*58))
symbolq = con.execute("select symbol, count(symbol) from pebs_ll group by symbol order by -count(symbol)")
for row in symbolq:
print("%32s %8d %s" % (row[0], row[1], num2sym(row[1])))
# Group by dse
dseq = con.execute("select dse, count(dse) from pebs_ll group by dse order by -count(dse)")
print("\n%32s %8s %16s\n%s" % ("dse", "number", "histogram", "="*58))
for row in dseq:
print("%32s %8d %s" % (row[0], row[1], num2sym(row[1])))
# Group by latency
latq = con.execute("select lat, count(lat) from pebs_ll group by lat order by lat")
print("\n%32s %8s %16s\n%s" % ("latency", "number", "histogram", "="*58))
for row in latq:
print("%32s %8d %s" % (row[0], row[1], num2sym(row[1])))
def trace_unhandled(event_name, context, event_fields_dict):
print (' '.join(['%s=%s'%(k,str(v))for k,v in sorted(event_fields_dict.items())]))
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,799 @@
# export-to-sqlite.py: export perf data to a sqlite3 database
# Copyright (c) 2017, Intel Corporation.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms and conditions of the GNU General Public License,
# version 2, as published by the Free Software Foundation.
#
# This program is distributed in the hope 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 for
# more details.
from __future__ import print_function
import os
import sys
import struct
import datetime
# To use this script you will need to have installed package python-pyside which
# provides LGPL-licensed Python bindings for Qt. You will also need the package
# libqt4-sql-sqlite for Qt sqlite3 support.
#
# Examples of installing pyside:
#
# ubuntu:
#
# $ sudo apt-get install python-pyside.qtsql libqt4-sql-psql
#
# Alternately, to use Python3 and/or pyside 2, one of the following:
#
# $ sudo apt-get install python3-pyside.qtsql libqt4-sql-psql
# $ sudo apt-get install python-pyside2.qtsql libqt5sql5-psql
# $ sudo apt-get install python3-pyside2.qtsql libqt5sql5-psql
# fedora:
#
# $ sudo yum install python-pyside
#
# Alternately, to use Python3 and/or pyside 2, one of the following:
# $ sudo yum install python3-pyside
# $ pip install --user PySide2
# $ pip3 install --user PySide2
#
# An example of using this script with Intel PT:
#
# $ perf record -e intel_pt//u ls
# $ perf script -s ~/libexec/perf-core/scripts/python/export-to-sqlite.py pt_example branches calls
# 2017-07-31 14:26:07.326913 Creating database...
# 2017-07-31 14:26:07.538097 Writing records...
# 2017-07-31 14:26:09.889292 Adding indexes
# 2017-07-31 14:26:09.958746 Done
#
# To browse the database, sqlite3 can be used e.g.
#
# $ sqlite3 pt_example
# sqlite> .header on
# sqlite> select * from samples_view where id < 10;
# sqlite> .mode column
# sqlite> select * from samples_view where id < 10;
# sqlite> .tables
# sqlite> .schema samples_view
# sqlite> .quit
#
# An example of using the database is provided by the script
# exported-sql-viewer.py. Refer to that script for details.
#
# The database structure is practically the same as created by the script
# export-to-postgresql.py. Refer to that script for details. A notable
# difference is the 'transaction' column of the 'samples' table which is
# renamed 'transaction_' in sqlite because 'transaction' is a reserved word.
pyside_version_1 = True
if not "pyside-version-1" in sys.argv:
try:
from PySide2.QtSql import *
pyside_version_1 = False
except:
pass
if pyside_version_1:
from PySide.QtSql import *
sys.path.append(os.environ['PERF_EXEC_PATH'] + \
'/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
# These perf imports are not used at present
#from perf_trace_context import *
#from Core import *
perf_db_export_mode = True
perf_db_export_calls = False
perf_db_export_callchains = False
def printerr(*args, **keyword_args):
print(*args, file=sys.stderr, **keyword_args)
def printdate(*args, **kw_args):
print(datetime.datetime.today(), *args, sep=' ', **kw_args)
def usage():
printerr("Usage is: export-to-sqlite.py <database name> [<columns>] [<calls>] [<callchains>] [<pyside-version-1>]");
printerr("where: columns 'all' or 'branches'");
printerr(" calls 'calls' => create calls and call_paths table");
printerr(" callchains 'callchains' => create call_paths table");
printerr(" pyside-version-1 'pyside-version-1' => use pyside version 1");
raise Exception("Too few or bad arguments")
if (len(sys.argv) < 2):
usage()
dbname = sys.argv[1]
if (len(sys.argv) >= 3):
columns = sys.argv[2]
else:
columns = "all"
if columns not in ("all", "branches"):
usage()
branches = (columns == "branches")
for i in range(3,len(sys.argv)):
if (sys.argv[i] == "calls"):
perf_db_export_calls = True
elif (sys.argv[i] == "callchains"):
perf_db_export_callchains = True
elif (sys.argv[i] == "pyside-version-1"):
pass
else:
usage()
def do_query(q, s):
if (q.exec_(s)):
return
raise Exception("Query failed: " + q.lastError().text())
def do_query_(q):
if (q.exec_()):
return
raise Exception("Query failed: " + q.lastError().text())
printdate("Creating database ...")
db_exists = False
try:
f = open(dbname)
f.close()
db_exists = True
except:
pass
if db_exists:
raise Exception(dbname + " already exists")
db = QSqlDatabase.addDatabase('QSQLITE')
db.setDatabaseName(dbname)
db.open()
query = QSqlQuery(db)
do_query(query, 'PRAGMA journal_mode = OFF')
do_query(query, 'BEGIN TRANSACTION')
do_query(query, 'CREATE TABLE selected_events ('
'id integer NOT NULL PRIMARY KEY,'
'name varchar(80))')
do_query(query, 'CREATE TABLE machines ('
'id integer NOT NULL PRIMARY KEY,'
'pid integer,'
'root_dir varchar(4096))')
do_query(query, 'CREATE TABLE threads ('
'id integer NOT NULL PRIMARY KEY,'
'machine_id bigint,'
'process_id bigint,'
'pid integer,'
'tid integer)')
do_query(query, 'CREATE TABLE comms ('
'id integer NOT NULL PRIMARY KEY,'
'comm varchar(16),'
'c_thread_id bigint,'
'c_time bigint,'
'exec_flag boolean)')
do_query(query, 'CREATE TABLE comm_threads ('
'id integer NOT NULL PRIMARY KEY,'
'comm_id bigint,'
'thread_id bigint)')
do_query(query, 'CREATE TABLE dsos ('
'id integer NOT NULL PRIMARY KEY,'
'machine_id bigint,'
'short_name varchar(256),'
'long_name varchar(4096),'
'build_id varchar(64))')
do_query(query, 'CREATE TABLE symbols ('
'id integer NOT NULL PRIMARY KEY,'
'dso_id bigint,'
'sym_start bigint,'
'sym_end bigint,'
'binding integer,'
'name varchar(2048))')
do_query(query, 'CREATE TABLE branch_types ('
'id integer NOT NULL PRIMARY KEY,'
'name varchar(80))')
if branches:
do_query(query, 'CREATE TABLE samples ('
'id integer NOT NULL PRIMARY KEY,'
'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,'
'branch_type integer,'
'in_tx boolean,'
'call_path_id bigint,'
'insn_count bigint,'
'cyc_count bigint,'
'flags integer)')
else:
do_query(query, 'CREATE TABLE samples ('
'id integer NOT NULL PRIMARY KEY,'
'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)')
if perf_db_export_calls or perf_db_export_callchains:
do_query(query, 'CREATE TABLE call_paths ('
'id integer NOT NULL PRIMARY KEY,'
'parent_id bigint,'
'symbol_id bigint,'
'ip bigint)')
if perf_db_export_calls:
do_query(query, 'CREATE TABLE calls ('
'id integer NOT NULL PRIMARY KEY,'
'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)')
do_query(query, 'CREATE TABLE ptwrite ('
'id integer NOT NULL PRIMARY KEY,'
'payload bigint,'
'exact_ip integer)')
do_query(query, 'CREATE TABLE cbr ('
'id integer NOT NULL PRIMARY KEY,'
'cbr integer,'
'mhz integer,'
'percent integer)')
do_query(query, 'CREATE TABLE mwait ('
'id integer NOT NULL PRIMARY KEY,'
'hints integer,'
'extensions integer)')
do_query(query, 'CREATE TABLE pwre ('
'id integer NOT NULL PRIMARY KEY,'
'cstate integer,'
'subcstate integer,'
'hw integer)')
do_query(query, 'CREATE TABLE exstop ('
'id integer NOT NULL PRIMARY KEY,'
'exact_ip integer)')
do_query(query, 'CREATE TABLE pwrx ('
'id integer NOT NULL PRIMARY KEY,'
'deepest_cstate integer,'
'last_cstate integer,'
'wake_reason integer)')
do_query(query, 'CREATE TABLE context_switches ('
'id integer NOT NULL PRIMARY KEY,'
'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)')
# printf was added to sqlite in version 3.8.3
sqlite_has_printf = False
try:
do_query(query, 'SELECT printf("") FROM machines')
sqlite_has_printf = True
except:
pass
def emit_to_hex(x):
if sqlite_has_printf:
return 'printf("%x", ' + x + ')'
else:
return x
do_query(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')
do_query(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')
do_query(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')
do_query(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')
do_query(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')
if perf_db_export_calls or perf_db_export_callchains:
do_query(query, 'CREATE VIEW call_paths_view AS '
'SELECT '
'c.id,'
+ emit_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,'
+ emit_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 INNER JOIN call_paths p ON p.id = c.parent_id')
if perf_db_export_calls:
do_query(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,'
+ emit_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 ROUND(CAST(insn_count AS FLOAT) / cyc_count, 2) 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=6 THEN \'jump\' ELSE flags END AS flags,'
'parent_call_path_id,'
'calls.parent_id'
' FROM calls INNER JOIN call_paths ON call_paths.id = call_path_id')
do_query(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,'
+ emit_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,'
+ emit_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,'
'insn_count,'
'cyc_count,'
'CASE WHEN cyc_count=0 THEN CAST(0 AS FLOAT) ELSE ROUND(CAST(insn_count AS FLOAT) / cyc_count, 2) END AS IPC,'
'flags'
' FROM samples')
do_query(query, 'CREATE VIEW ptwrite_view AS '
'SELECT '
'ptwrite.id,'
'time,'
'cpu,'
+ emit_to_hex('payload') + ' AS payload_hex,'
'CASE WHEN exact_ip=0 THEN \'False\' ELSE \'True\' END AS exact_ip'
' FROM ptwrite'
' INNER JOIN samples ON samples.id = ptwrite.id')
do_query(query, 'CREATE VIEW cbr_view AS '
'SELECT '
'cbr.id,'
'time,'
'cpu,'
'cbr,'
'mhz,'
'percent'
' FROM cbr'
' INNER JOIN samples ON samples.id = cbr.id')
do_query(query, 'CREATE VIEW mwait_view AS '
'SELECT '
'mwait.id,'
'time,'
'cpu,'
+ emit_to_hex('hints') + ' AS hints_hex,'
+ emit_to_hex('extensions') + ' AS extensions_hex'
' FROM mwait'
' INNER JOIN samples ON samples.id = mwait.id')
do_query(query, 'CREATE VIEW pwre_view AS '
'SELECT '
'pwre.id,'
'time,'
'cpu,'
'cstate,'
'subcstate,'
'CASE WHEN hw=0 THEN \'False\' ELSE \'True\' END AS hw'
' FROM pwre'
' INNER JOIN samples ON samples.id = pwre.id')
do_query(query, 'CREATE VIEW exstop_view AS '
'SELECT '
'exstop.id,'
'time,'
'cpu,'
'CASE WHEN exact_ip=0 THEN \'False\' ELSE \'True\' END AS exact_ip'
' FROM exstop'
' INNER JOIN samples ON samples.id = exstop.id')
do_query(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\''
' ELSE wake_reason '
'END AS wake_reason'
' FROM pwrx'
' INNER JOIN samples ON samples.id = pwrx.id')
do_query(query, 'CREATE VIEW power_events_view AS '
'SELECT '
'samples.id,'
'time,'
'cpu,'
'selected_events.name AS event,'
'CASE WHEN selected_events.name=\'cbr\' THEN (SELECT cbr FROM cbr WHERE cbr.id = samples.id) ELSE "" END AS cbr,'
'CASE WHEN selected_events.name=\'cbr\' THEN (SELECT mhz FROM cbr WHERE cbr.id = samples.id) ELSE "" END AS mhz,'
'CASE WHEN selected_events.name=\'cbr\' THEN (SELECT percent FROM cbr WHERE cbr.id = samples.id) ELSE "" END AS percent,'
'CASE WHEN selected_events.name=\'mwait\' THEN (SELECT ' + emit_to_hex('hints') + ' FROM mwait WHERE mwait.id = samples.id) ELSE "" END AS hints_hex,'
'CASE WHEN selected_events.name=\'mwait\' THEN (SELECT ' + emit_to_hex('extensions') + ' FROM mwait WHERE mwait.id = samples.id) ELSE "" END AS extensions_hex,'
'CASE WHEN selected_events.name=\'pwre\' THEN (SELECT cstate FROM pwre WHERE pwre.id = samples.id) ELSE "" END AS cstate,'
'CASE WHEN selected_events.name=\'pwre\' THEN (SELECT subcstate FROM pwre WHERE pwre.id = samples.id) ELSE "" END AS subcstate,'
'CASE WHEN selected_events.name=\'pwre\' THEN (SELECT hw FROM pwre WHERE pwre.id = samples.id) ELSE "" END AS hw,'
'CASE WHEN selected_events.name=\'exstop\' THEN (SELECT exact_ip FROM exstop WHERE exstop.id = samples.id) ELSE "" END AS exact_ip,'
'CASE WHEN selected_events.name=\'pwrx\' THEN (SELECT deepest_cstate FROM pwrx WHERE pwrx.id = samples.id) ELSE "" END AS deepest_cstate,'
'CASE WHEN selected_events.name=\'pwrx\' THEN (SELECT last_cstate FROM pwrx WHERE pwrx.id = samples.id) ELSE "" END AS last_cstate,'
'CASE WHEN selected_events.name=\'pwrx\' THEN (SELECT '
'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\''
' ELSE wake_reason '
'END'
' FROM pwrx WHERE pwrx.id = samples.id) ELSE "" END AS wake_reason'
' FROM samples'
' INNER JOIN selected_events ON selected_events.id = evsel_id'
' WHERE selected_events.name IN (\'cbr\',\'mwait\',\'exstop\',\'pwre\',\'pwrx\')')
do_query(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 context_switches.flags = 0 THEN \'in\''
' WHEN context_switches.flags = 1 THEN \'out\''
' WHEN context_switches.flags = 3 THEN \'out preempt\''
' ELSE context_switches.flags '
'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')
do_query(query, 'END TRANSACTION')
evsel_query = QSqlQuery(db)
evsel_query.prepare("INSERT INTO selected_events VALUES (?, ?)")
machine_query = QSqlQuery(db)
machine_query.prepare("INSERT INTO machines VALUES (?, ?, ?)")
thread_query = QSqlQuery(db)
thread_query.prepare("INSERT INTO threads VALUES (?, ?, ?, ?, ?)")
comm_query = QSqlQuery(db)
comm_query.prepare("INSERT INTO comms VALUES (?, ?, ?, ?, ?)")
comm_thread_query = QSqlQuery(db)
comm_thread_query.prepare("INSERT INTO comm_threads VALUES (?, ?, ?)")
dso_query = QSqlQuery(db)
dso_query.prepare("INSERT INTO dsos VALUES (?, ?, ?, ?, ?)")
symbol_query = QSqlQuery(db)
symbol_query.prepare("INSERT INTO symbols VALUES (?, ?, ?, ?, ?, ?)")
branch_type_query = QSqlQuery(db)
branch_type_query.prepare("INSERT INTO branch_types VALUES (?, ?)")
sample_query = QSqlQuery(db)
if branches:
sample_query.prepare("INSERT INTO samples VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
else:
sample_query.prepare("INSERT INTO samples VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
if perf_db_export_calls or perf_db_export_callchains:
call_path_query = QSqlQuery(db)
call_path_query.prepare("INSERT INTO call_paths VALUES (?, ?, ?, ?)")
if perf_db_export_calls:
call_query = QSqlQuery(db)
call_query.prepare("INSERT INTO calls VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
ptwrite_query = QSqlQuery(db)
ptwrite_query.prepare("INSERT INTO ptwrite VALUES (?, ?, ?)")
cbr_query = QSqlQuery(db)
cbr_query.prepare("INSERT INTO cbr VALUES (?, ?, ?, ?)")
mwait_query = QSqlQuery(db)
mwait_query.prepare("INSERT INTO mwait VALUES (?, ?, ?)")
pwre_query = QSqlQuery(db)
pwre_query.prepare("INSERT INTO pwre VALUES (?, ?, ?, ?)")
exstop_query = QSqlQuery(db)
exstop_query.prepare("INSERT INTO exstop VALUES (?, ?)")
pwrx_query = QSqlQuery(db)
pwrx_query.prepare("INSERT INTO pwrx VALUES (?, ?, ?, ?)")
context_switch_query = QSqlQuery(db)
context_switch_query.prepare("INSERT INTO context_switches VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)")
def trace_begin():
printdate("Writing records...")
do_query(query, 'BEGIN TRANSACTION')
# id == 0 means unknown. It is easier to create records for them than replace the zeroes with NULLs
evsel_table(0, "unknown")
machine_table(0, 0, "unknown")
thread_table(0, 0, 0, -1, -1)
comm_table(0, "unknown", 0, 0, 0)
dso_table(0, 0, "unknown", "unknown", "")
symbol_table(0, 0, 0, 0, 0, "unknown")
sample_table(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
if perf_db_export_calls or perf_db_export_callchains:
call_path_table(0, 0, 0, 0)
call_return_table(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
unhandled_count = 0
def is_table_empty(table_name):
do_query(query, 'SELECT * FROM ' + table_name + ' LIMIT 1');
if query.next():
return False
return True
def drop(table_name):
do_query(query, 'DROP VIEW ' + table_name + '_view');
do_query(query, 'DROP TABLE ' + table_name);
def trace_end():
do_query(query, 'END TRANSACTION')
printdate("Adding indexes")
if perf_db_export_calls:
do_query(query, 'CREATE INDEX pcpid_idx ON calls (parent_call_path_id)')
do_query(query, 'CREATE INDEX pid_idx ON calls (parent_id)')
do_query(query, 'ALTER TABLE comms ADD has_calls boolean')
do_query(query, 'UPDATE comms SET has_calls = 1 WHERE comms.id IN (SELECT DISTINCT comm_id FROM calls)')
printdate("Dropping unused tables")
if is_table_empty("ptwrite"):
drop("ptwrite")
if is_table_empty("mwait") and is_table_empty("pwre") and is_table_empty("exstop") and is_table_empty("pwrx"):
do_query(query, 'DROP VIEW power_events_view');
drop("mwait")
drop("pwre")
drop("exstop")
drop("pwrx")
if is_table_empty("cbr"):
drop("cbr")
if is_table_empty("context_switches"):
drop("context_switches")
if (unhandled_count):
printdate("Warning: ", unhandled_count, " unhandled events")
printdate("Done")
def trace_unhandled(event_name, context, event_fields_dict):
global unhandled_count
unhandled_count += 1
def sched__sched_switch(*x):
pass
def bind_exec(q, n, x):
for xx in x[0:n]:
q.addBindValue(str(xx))
do_query_(q)
def evsel_table(*x):
bind_exec(evsel_query, 2, x)
def machine_table(*x):
bind_exec(machine_query, 3, x)
def thread_table(*x):
bind_exec(thread_query, 5, x)
def comm_table(*x):
bind_exec(comm_query, 5, x)
def comm_thread_table(*x):
bind_exec(comm_thread_query, 3, x)
def dso_table(*x):
bind_exec(dso_query, 5, x)
def symbol_table(*x):
bind_exec(symbol_query, 6, x)
def branch_type_table(*x):
bind_exec(branch_type_query, 2, x)
def sample_table(*x):
if branches:
for xx in x[0:15]:
sample_query.addBindValue(str(xx))
for xx in x[19:25]:
sample_query.addBindValue(str(xx))
do_query_(sample_query)
else:
bind_exec(sample_query, 25, x)
def call_path_table(*x):
bind_exec(call_path_query, 4, x)
def call_return_table(*x):
bind_exec(call_query, 14, x)
def ptwrite(id, raw_buf):
data = struct.unpack_from("<IQ", raw_buf)
flags = data[0]
payload = data[1]
exact_ip = flags & 1
ptwrite_query.addBindValue(str(id))
ptwrite_query.addBindValue(str(payload))
ptwrite_query.addBindValue(str(exact_ip))
do_query_(ptwrite_query)
def cbr(id, raw_buf):
data = struct.unpack_from("<BBBBII", raw_buf)
cbr = data[0]
MHz = (data[4] + 500) / 1000
percent = ((cbr * 1000 / data[2]) + 5) / 10
cbr_query.addBindValue(str(id))
cbr_query.addBindValue(str(cbr))
cbr_query.addBindValue(str(MHz))
cbr_query.addBindValue(str(percent))
do_query_(cbr_query)
def mwait(id, raw_buf):
data = struct.unpack_from("<IQ", raw_buf)
payload = data[1]
hints = payload & 0xff
extensions = (payload >> 32) & 0x3
mwait_query.addBindValue(str(id))
mwait_query.addBindValue(str(hints))
mwait_query.addBindValue(str(extensions))
do_query_(mwait_query)
def pwre(id, raw_buf):
data = struct.unpack_from("<IQ", raw_buf)
payload = data[1]
hw = (payload >> 7) & 1
cstate = (payload >> 12) & 0xf
subcstate = (payload >> 8) & 0xf
pwre_query.addBindValue(str(id))
pwre_query.addBindValue(str(cstate))
pwre_query.addBindValue(str(subcstate))
pwre_query.addBindValue(str(hw))
do_query_(pwre_query)
def exstop(id, raw_buf):
data = struct.unpack_from("<I", raw_buf)
flags = data[0]
exact_ip = flags & 1
exstop_query.addBindValue(str(id))
exstop_query.addBindValue(str(exact_ip))
do_query_(exstop_query)
def pwrx(id, raw_buf):
data = struct.unpack_from("<IQ", raw_buf)
payload = data[1]
deepest_cstate = payload & 0xf
last_cstate = (payload >> 4) & 0xf
wake_reason = (payload >> 8) & 0xf
pwrx_query.addBindValue(str(id))
pwrx_query.addBindValue(str(deepest_cstate))
pwrx_query.addBindValue(str(last_cstate))
pwrx_query.addBindValue(str(wake_reason))
do_query_(pwrx_query)
def synth_data(id, config, raw_buf, *x):
if config == 0:
ptwrite(id, raw_buf)
elif config == 1:
mwait(id, raw_buf)
elif config == 2:
pwre(id, raw_buf)
elif config == 3:
exstop(id, raw_buf)
elif config == 4:
pwrx(id, raw_buf)
elif config == 5:
cbr(id, raw_buf)
def context_switch_table(*x):
bind_exec(context_switch_query, 9, x)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,79 @@
# failed system call counts, by pid
# (c) 2010, Tom Zanussi <tzanussi@gmail.com>
# Licensed under the terms of the GNU GPL License version 2
#
# Displays system-wide failed system call totals, broken down by pid.
# If a [comm] arg is specified, only syscalls called by [comm] are displayed.
from __future__ import print_function
import os
import sys
sys.path.append(os.environ['PERF_EXEC_PATH'] + \
'/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
from perf_trace_context import *
from Core import *
from Util import *
usage = "perf script -s syscall-counts-by-pid.py [comm|pid]\n";
for_comm = None
for_pid = None
if len(sys.argv) > 2:
sys.exit(usage)
if len(sys.argv) > 1:
try:
for_pid = int(sys.argv[1])
except:
for_comm = sys.argv[1]
syscalls = autodict()
def trace_begin():
print("Press control+C to stop and show the summary")
def trace_end():
print_error_totals()
def raw_syscalls__sys_exit(event_name, context, common_cpu,
common_secs, common_nsecs, common_pid, common_comm,
common_callchain, id, ret):
if (for_comm and common_comm != for_comm) or \
(for_pid and common_pid != for_pid ):
return
if ret < 0:
try:
syscalls[common_comm][common_pid][id][ret] += 1
except TypeError:
syscalls[common_comm][common_pid][id][ret] = 1
def syscalls__sys_exit(event_name, context, common_cpu,
common_secs, common_nsecs, common_pid, common_comm,
id, ret):
raw_syscalls__sys_exit(**locals())
def print_error_totals():
if for_comm is not None:
print("\nsyscall errors for %s:\n" % (for_comm))
else:
print("\nsyscall errors:\n")
print("%-30s %10s" % ("comm [pid]", "count"))
print("%-30s %10s" % ("------------------------------", "----------"))
comm_keys = syscalls.keys()
for comm in comm_keys:
pid_keys = syscalls[comm].keys()
for pid in pid_keys:
print("\n%s [%d]" % (comm, pid))
id_keys = syscalls[comm][pid].keys()
for id in id_keys:
print(" syscall: %-16s" % syscall_name(id))
ret_keys = syscalls[comm][pid][id].keys()
for ret, val in sorted(syscalls[comm][pid][id].items(), key = lambda kv: (kv[1], kv[0]), reverse = True):
print(" err = %-20s %10d" % (strerror(ret), val))
+267
View File
@@ -0,0 +1,267 @@
# flamegraph.py - create flame graphs from perf samples
# SPDX-License-Identifier: GPL-2.0
#
# Usage:
#
# perf record -a -g -F 99 sleep 60
# perf script report flamegraph
#
# Combined:
#
# perf script flamegraph -a -F 99 sleep 60
#
# Written by Andreas Gerstmayr <agerstmayr@redhat.com>
# Flame Graphs invented by Brendan Gregg <bgregg@netflix.com>
# Works in tandem with d3-flame-graph by Martin Spier <mspier@netflix.com>
#
# pylint: disable=missing-module-docstring
# pylint: disable=missing-class-docstring
# pylint: disable=missing-function-docstring
import argparse
import hashlib
import io
import json
import os
import subprocess
import sys
from typing import Dict, Optional, Union
import urllib.request
MINIMAL_HTML = """<head>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/d3-flamegraph.css">
</head>
<body>
<div id="chart"></div>
<script type="text/javascript" src="https://d3js.org/d3.v7.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/d3-flamegraph.min.js"></script>
<script type="text/javascript">
const stacks = [/** @flamegraph_json **/];
// Note, options is unused.
const options = [/** @options_json **/];
var chart = flamegraph();
d3.select("#chart")
.datum(stacks[0])
.call(chart);
</script>
</body>
"""
# pylint: disable=too-few-public-methods
class Node:
def __init__(self, name: str, libtype: str):
self.name = name
# "root" | "kernel" | ""
# "" indicates user space
self.libtype = libtype
self.value: int = 0
self.children: list[Node] = []
def to_json(self) -> Dict[str, Union[str, int, list[Dict]]]:
return {
"n": self.name,
"l": self.libtype,
"v": self.value,
"c": [x.to_json() for x in self.children]
}
class FlameGraphCLI:
def __init__(self, args):
self.args = args
self.stack = Node("all", "root")
@staticmethod
def get_libtype_from_dso(dso: Optional[str]) -> str:
"""
when kernel-debuginfo is installed,
dso points to /usr/lib/debug/lib/modules/*/vmlinux
"""
if dso and (dso == "[kernel.kallsyms]" or dso.endswith("/vmlinux")):
return "kernel"
return ""
@staticmethod
def find_or_create_node(node: Node, name: str, libtype: str) -> Node:
for child in node.children:
if child.name == name:
return child
child = Node(name, libtype)
node.children.append(child)
return child
def process_event(self, event) -> None:
# ignore events where the event name does not match
# the one specified by the user
if self.args.event_name and event.get("ev_name") != self.args.event_name:
return
pid = event.get("sample", {}).get("pid", 0)
# event["dso"] sometimes contains /usr/lib/debug/lib/modules/*/vmlinux
# for user-space processes; let's use pid for kernel or user-space distinction
if pid == 0:
comm = event["comm"]
libtype = "kernel"
else:
comm = f"{event['comm']} ({pid})"
libtype = ""
node = self.find_or_create_node(self.stack, comm, libtype)
if "callchain" in event:
for entry in reversed(event["callchain"]):
name = entry.get("sym", {}).get("name", "[unknown]")
libtype = self.get_libtype_from_dso(entry.get("dso"))
node = self.find_or_create_node(node, name, libtype)
else:
name = event.get("symbol", "[unknown]")
libtype = self.get_libtype_from_dso(event.get("dso"))
node = self.find_or_create_node(node, name, libtype)
node.value += 1
def get_report_header(self) -> str:
if self.args.input == "-":
# when this script is invoked with "perf script flamegraph",
# no perf.data is created and we cannot read the header of it
return ""
try:
# if the file name other than perf.data is given,
# we read the header of that file
if self.args.input:
output = subprocess.check_output(["perf", "report", "--header-only",
"-i", self.args.input])
else:
output = subprocess.check_output(["perf", "report", "--header-only"])
result = output.decode("utf-8")
if self.args.event_name:
result += "\nFocused event: " + self.args.event_name
return result
except Exception as err: # pylint: disable=broad-except
print(f"Error reading report header: {err}", file=sys.stderr)
return ""
def trace_end(self) -> None:
stacks_json = json.dumps(self.stack, default=lambda x: x.to_json())
if self.args.format == "html":
report_header = self.get_report_header()
options = {
"colorscheme": self.args.colorscheme,
"context": report_header
}
options_json = json.dumps(options)
template_md5sum = None
if self.args.format == "html":
if os.path.isfile(self.args.template):
template = f"file://{self.args.template}"
else:
if not self.args.allow_download:
print(f"""Warning: Flame Graph template '{self.args.template}'
does not exist. To avoid this please install a package such as the
js-d3-flame-graph or libjs-d3-flame-graph, specify an existing flame
graph template (--template PATH) or use another output format (--format
FORMAT).""",
file=sys.stderr)
if self.args.input == "-":
print(
"""Not attempting to download Flame Graph template as script command line
input is disabled due to using live mode. If you want to download the
template retry without live mode. For example, use 'perf record -a -g
-F 99 sleep 60' and 'perf script report flamegraph'. Alternatively,
download the template from:
https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/templates/d3-flamegraph-base.html
and place it at:
/usr/share/d3-flame-graph/d3-flamegraph-base.html""",
file=sys.stderr)
sys.exit(1)
s = None
while s not in ["y", "n"]:
s = input("Do you wish to download a template from cdn.jsdelivr.net?" +
"(this warning can be suppressed with --allow-download) [yn] "
).lower()
if s == "n":
sys.exit(1)
template = ("https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/templates/"
"d3-flamegraph-base.html")
template_md5sum = "143e0d06ba69b8370b9848dcd6ae3f36"
try:
with urllib.request.urlopen(template) as url_template:
output_str = "".join([
l.decode("utf-8") for l in url_template.readlines()
])
except Exception as err:
print(f"Error reading template {template}: {err}\n"
"a minimal flame graph will be generated", file=sys.stderr)
output_str = MINIMAL_HTML
template_md5sum = None
if template_md5sum:
download_md5sum = hashlib.md5(output_str.encode("utf-8")).hexdigest()
if download_md5sum != template_md5sum:
s = None
while s not in ["y", "n"]:
s = input(f"""Unexpected template md5sum.
{download_md5sum} != {template_md5sum}, for:
{output_str}
continue?[yn] """).lower()
if s == "n":
sys.exit(1)
output_str = output_str.replace("/** @options_json **/", options_json)
output_str = output_str.replace("/** @flamegraph_json **/", stacks_json)
output_fn = self.args.output or "flamegraph.html"
else:
output_str = stacks_json
output_fn = self.args.output or "stacks.json"
if output_fn == "-":
with io.open(sys.stdout.fileno(), "w", encoding="utf-8", closefd=False) as out:
out.write(output_str)
else:
print(f"dumping data to {output_fn}")
try:
with io.open(output_fn, "w", encoding="utf-8") as out:
out.write(output_str)
except IOError as err:
print(f"Error writing output file: {err}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Create flame graphs.")
parser.add_argument("-f", "--format",
default="html", choices=["json", "html"],
help="output file format")
parser.add_argument("-o", "--output",
help="output file name")
parser.add_argument("--template",
default="/usr/share/d3-flame-graph/d3-flamegraph-base.html",
help="path to flame graph HTML template")
parser.add_argument("--colorscheme",
default="blue-green",
help="flame graph color scheme",
choices=["blue-green", "orange"])
parser.add_argument("-i", "--input",
help=argparse.SUPPRESS)
parser.add_argument("--allow-download",
default=False,
action="store_true",
help="allow unprompted downloading of HTML template")
parser.add_argument("-e", "--event",
default="",
dest="event_name",
type=str,
help="specify the event to generate flamegraph for")
cli_args = parser.parse_args()
cli = FlameGraphCLI(cli_args)
process_event = cli.process_event
trace_end = cli.trace_end
@@ -0,0 +1,57 @@
# futex contention
# (c) 2010, Arnaldo Carvalho de Melo <acme@redhat.com>
# Licensed under the terms of the GNU GPL License version 2
#
# Translation of:
#
# http://sourceware.org/systemtap/wiki/WSFutexContention
#
# to perf python scripting.
#
# Measures futex contention
from __future__ import print_function
import os
import sys
sys.path.append(os.environ['PERF_EXEC_PATH'] +
'/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
from Util import *
process_names = {}
thread_thislock = {}
thread_blocktime = {}
lock_waits = {} # long-lived stats on (tid,lock) blockage elapsed time
process_names = {} # long-lived pid-to-execname mapping
def syscalls__sys_enter_futex(event, ctxt, cpu, s, ns, tid, comm, callchain,
nr, uaddr, op, val, utime, uaddr2, val3):
cmd = op & FUTEX_CMD_MASK
if cmd != FUTEX_WAIT:
return # we don't care about originators of WAKE events
process_names[tid] = comm
thread_thislock[tid] = uaddr
thread_blocktime[tid] = nsecs(s, ns)
def syscalls__sys_exit_futex(event, ctxt, cpu, s, ns, tid, comm, callchain,
nr, ret):
if tid in thread_blocktime:
elapsed = nsecs(s, ns) - thread_blocktime[tid]
add_stats(lock_waits, (tid, thread_thislock[tid]), elapsed)
del thread_blocktime[tid]
del thread_thislock[tid]
def trace_begin():
print("Press control+C to stop and show the summary")
def trace_end():
for (tid, lock) in lock_waits:
min, max, avg, count = lock_waits[tid, lock]
print("%s[%d] lock %x contended %d times, %d avg ns [max: %d ns, min %d ns]" %
(process_names[tid], tid, lock, count, avg, max, min))
@@ -0,0 +1,395 @@
# gecko.py - Convert perf record output to Firefox's gecko profile format
# SPDX-License-Identifier: GPL-2.0
#
# The script converts perf.data to Gecko Profile Format,
# which can be read by https://profiler.firefox.com/.
#
# Usage:
#
# perf record -a -g -F 99 sleep 60
# perf script report gecko
#
# Combined:
#
# perf script gecko -F 99 -a sleep 60
import os
import sys
import time
import json
import string
import random
import argparse
import threading
import webbrowser
import urllib.parse
from os import system
from functools import reduce
from dataclasses import dataclass, field
from http.server import HTTPServer, SimpleHTTPRequestHandler, test
from typing import List, Dict, Optional, NamedTuple, Set, Tuple, Any
# Add the Perf-Trace-Util library to the Python path
sys.path.append(os.environ['PERF_EXEC_PATH'] + \
'/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
from perf_trace_context import *
from Core import *
StringID = int
StackID = int
FrameID = int
CategoryID = int
Milliseconds = float
# start_time is intialiazed only once for the all event traces.
start_time = None
# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/profile.js#L425
# Follow Brendan Gregg's Flamegraph convention: orange for kernel and yellow for user space by default.
CATEGORIES = None
# The product name is used by the profiler UI to show the Operating system and Processor.
PRODUCT = os.popen('uname -op').read().strip()
# store the output file
output_file = None
# Here key = tid, value = Thread
tid_to_thread = dict()
# The HTTP server is used to serve the profile to the profiler UI.
http_server_thread = None
# The category index is used by the profiler UI to show the color of the flame graph.
USER_CATEGORY_INDEX = 0
KERNEL_CATEGORY_INDEX = 1
# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L156
class Frame(NamedTuple):
string_id: StringID
relevantForJS: bool
innerWindowID: int
implementation: None
optimizations: None
line: None
column: None
category: CategoryID
subcategory: int
# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L216
class Stack(NamedTuple):
prefix_id: Optional[StackID]
frame_id: FrameID
# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L90
class Sample(NamedTuple):
stack_id: Optional[StackID]
time_ms: Milliseconds
responsiveness: int
@dataclass
class Thread:
"""A builder for a profile of the thread.
Attributes:
comm: Thread command-line (name).
pid: process ID of containing process.
tid: thread ID.
samples: Timeline of profile samples.
frameTable: interned stack frame ID -> stack frame.
stringTable: interned string ID -> string.
stringMap: interned string -> string ID.
stackTable: interned stack ID -> stack.
stackMap: (stack prefix ID, leaf stack frame ID) -> interned Stack ID.
frameMap: Stack Frame string -> interned Frame ID.
comm: str
pid: int
tid: int
samples: List[Sample] = field(default_factory=list)
frameTable: List[Frame] = field(default_factory=list)
stringTable: List[str] = field(default_factory=list)
stringMap: Dict[str, int] = field(default_factory=dict)
stackTable: List[Stack] = field(default_factory=list)
stackMap: Dict[Tuple[Optional[int], int], int] = field(default_factory=dict)
frameMap: Dict[str, int] = field(default_factory=dict)
"""
comm: str
pid: int
tid: int
samples: List[Sample] = field(default_factory=list)
frameTable: List[Frame] = field(default_factory=list)
stringTable: List[str] = field(default_factory=list)
stringMap: Dict[str, int] = field(default_factory=dict)
stackTable: List[Stack] = field(default_factory=list)
stackMap: Dict[Tuple[Optional[int], int], int] = field(default_factory=dict)
frameMap: Dict[str, int] = field(default_factory=dict)
def _intern_stack(self, frame_id: int, prefix_id: Optional[int]) -> int:
"""Gets a matching stack, or saves the new stack. Returns a Stack ID."""
key = f"{frame_id}" if prefix_id is None else f"{frame_id},{prefix_id}"
# key = (prefix_id, frame_id)
stack_id = self.stackMap.get(key)
if stack_id is None:
# return stack_id
stack_id = len(self.stackTable)
self.stackTable.append(Stack(prefix_id=prefix_id, frame_id=frame_id))
self.stackMap[key] = stack_id
return stack_id
def _intern_string(self, string: str) -> int:
"""Gets a matching string, or saves the new string. Returns a String ID."""
string_id = self.stringMap.get(string)
if string_id is not None:
return string_id
string_id = len(self.stringTable)
self.stringTable.append(string)
self.stringMap[string] = string_id
return string_id
def _intern_frame(self, frame_str: str) -> int:
"""Gets a matching stack frame, or saves the new frame. Returns a Frame ID."""
frame_id = self.frameMap.get(frame_str)
if frame_id is not None:
return frame_id
frame_id = len(self.frameTable)
self.frameMap[frame_str] = frame_id
string_id = self._intern_string(frame_str)
symbol_name_to_category = KERNEL_CATEGORY_INDEX if frame_str.find('kallsyms') != -1 \
or frame_str.find('/vmlinux') != -1 \
or frame_str.endswith('.ko)') \
else USER_CATEGORY_INDEX
self.frameTable.append(Frame(
string_id=string_id,
relevantForJS=False,
innerWindowID=0,
implementation=None,
optimizations=None,
line=None,
column=None,
category=symbol_name_to_category,
subcategory=None,
))
return frame_id
def _add_sample(self, comm: str, stack: List[str], time_ms: Milliseconds) -> None:
"""Add a timestamped stack trace sample to the thread builder.
Args:
comm: command-line (name) of the thread at this sample
stack: sampled stack frames. Root first, leaf last.
time_ms: timestamp of sample in milliseconds.
"""
# Ihreads may not set their names right after they are created.
# Instead, they might do it later. In such situations, to use the latest name they have set.
if self.comm != comm:
self.comm = comm
prefix_stack_id = reduce(lambda prefix_id, frame: self._intern_stack
(self._intern_frame(frame), prefix_id), stack, None)
if prefix_stack_id is not None:
self.samples.append(Sample(stack_id=prefix_stack_id,
time_ms=time_ms,
responsiveness=0))
def _to_json_dict(self) -> Dict:
"""Converts current Thread to GeckoThread JSON format."""
# Gecko profile format is row-oriented data as List[List],
# And a schema for interpreting each index.
# Schema:
# https://github.com/firefox-devtools/profiler/blob/main/docs-developer/gecko-profile-format.md
# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L230
return {
"tid": self.tid,
"pid": self.pid,
"name": self.comm,
# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L51
"markers": {
"schema": {
"name": 0,
"startTime": 1,
"endTime": 2,
"phase": 3,
"category": 4,
"data": 5,
},
"data": [],
},
# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L90
"samples": {
"schema": {
"stack": 0,
"time": 1,
"responsiveness": 2,
},
"data": self.samples
},
# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L156
"frameTable": {
"schema": {
"location": 0,
"relevantForJS": 1,
"innerWindowID": 2,
"implementation": 3,
"optimizations": 4,
"line": 5,
"column": 6,
"category": 7,
"subcategory": 8,
},
"data": self.frameTable,
},
# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L216
"stackTable": {
"schema": {
"prefix": 0,
"frame": 1,
},
"data": self.stackTable,
},
"stringTable": self.stringTable,
"registerTime": 0,
"unregisterTime": None,
"processType": "default",
}
# Uses perf script python interface to parse each
# event and store the data in the thread builder.
def process_event(param_dict: Dict) -> None:
global start_time
global tid_to_thread
time_stamp = (param_dict['sample']['time'] // 1000) / 1000
pid = param_dict['sample']['pid']
tid = param_dict['sample']['tid']
comm = param_dict['comm']
# Start time is the time of the first sample
if not start_time:
start_time = time_stamp
# Parse and append the callchain of the current sample into a stack.
stack = []
if param_dict['callchain']:
for call in param_dict['callchain']:
if 'sym' not in call:
continue
stack.append(f'{call["sym"]["name"]} (in {call["dso"]})')
if len(stack) != 0:
# Reverse the stack, as root come first and the leaf at the end.
stack = stack[::-1]
# During perf record if -g is not used, the callchain is not available.
# In that case, the symbol and dso are available in the event parameters.
else:
func = param_dict['symbol'] if 'symbol' in param_dict else '[unknown]'
dso = param_dict['dso'] if 'dso' in param_dict else '[unknown]'
stack.append(f'{func} (in {dso})')
# Add sample to the specific thread.
thread = tid_to_thread.get(tid)
if thread is None:
thread = Thread(comm=comm, pid=pid, tid=tid)
tid_to_thread[tid] = thread
thread._add_sample(comm=comm, stack=stack, time_ms=time_stamp)
def trace_begin() -> None:
global output_file
if (output_file is None):
print("Staring Firefox Profiler on your default browser...")
global http_server_thread
http_server_thread = threading.Thread(target=test, args=(CORSRequestHandler, HTTPServer,))
http_server_thread.daemon = True
http_server_thread.start()
# Trace_end runs at the end and will be used to aggregate
# the data into the final json object and print it out to stdout.
def trace_end() -> None:
global output_file
threads = [thread._to_json_dict() for thread in tid_to_thread.values()]
# Schema: https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L305
gecko_profile_with_meta = {
"meta": {
"interval": 1,
"processType": 0,
"product": PRODUCT,
"stackwalk": 1,
"debug": 0,
"gcpoison": 0,
"asyncstack": 1,
"startTime": start_time,
"shutdownTime": None,
"version": 24,
"presymbolicated": True,
"categories": CATEGORIES,
"markerSchema": [],
},
"libs": [],
"threads": threads,
"processes": [],
"pausedRanges": [],
}
# launch the profiler on local host if not specified --save-only args, otherwise print to file
if (output_file is None):
output_file = 'gecko_profile.json'
with open(output_file, 'w') as f:
json.dump(gecko_profile_with_meta, f, indent=2)
launchFirefox(output_file)
time.sleep(1)
print(f'[ perf gecko: Captured and wrote into {output_file} ]')
else:
print(f'[ perf gecko: Captured and wrote into {output_file} ]')
with open(output_file, 'w') as f:
json.dump(gecko_profile_with_meta, f, indent=2)
# Used to enable Cross-Origin Resource Sharing (CORS) for requests coming from 'https://profiler.firefox.com', allowing it to access resources from this server.
class CORSRequestHandler(SimpleHTTPRequestHandler):
def end_headers (self):
self.send_header('Access-Control-Allow-Origin', 'https://profiler.firefox.com')
SimpleHTTPRequestHandler.end_headers(self)
# start a local server to serve the gecko_profile.json file to the profiler.firefox.com
def launchFirefox(file):
safe_string = urllib.parse.quote_plus(f'http://localhost:8000/{file}')
url = 'https://profiler.firefox.com/from-url/' + safe_string
webbrowser.open(f'{url}')
def main() -> None:
global output_file
global CATEGORIES
parser = argparse.ArgumentParser(description="Convert perf.data to Firefox\'s Gecko Profile format which can be uploaded to profiler.firefox.com for visualization")
# Add the command-line options
# Colors must be defined according to this:
# https://github.com/firefox-devtools/profiler/blob/50124adbfa488adba6e2674a8f2618cf34b59cd2/res/css/categories.css
parser.add_argument('--user-color', default='yellow', help='Color for the User category', choices=['yellow', 'blue', 'purple', 'green', 'orange', 'red', 'grey', 'magenta'])
parser.add_argument('--kernel-color', default='orange', help='Color for the Kernel category', choices=['yellow', 'blue', 'purple', 'green', 'orange', 'red', 'grey', 'magenta'])
# If --save-only is specified, the output will be saved to a file instead of opening Firefox's profiler directly.
parser.add_argument('--save-only', help='Save the output to a file instead of opening Firefox\'s profiler')
# Parse the command-line arguments
args = parser.parse_args()
# Access the values provided by the user
user_color = args.user_color
kernel_color = args.kernel_color
output_file = args.save_only
CATEGORIES = [
{
"name": 'User',
"color": user_color,
"subcategories": ['Other']
},
{
"name": 'Kernel',
"color": kernel_color,
"subcategories": ['Other']
},
]
if __name__ == '__main__':
main()
@@ -0,0 +1,494 @@
# SPDX-License-Identifier: GPL-2.0
# intel-pt-events.py: Print Intel PT Events including Power Events and PTWRITE
# Copyright (c) 2017-2021, Intel Corporation.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms and conditions of the GNU General Public License,
# version 2, as published by the Free Software Foundation.
#
# This program is distributed in the hope 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 for
# more details.
from __future__ import division, print_function
import io
import os
import sys
import struct
import argparse
import contextlib
from libxed import LibXED
from ctypes import create_string_buffer, addressof
sys.path.append(os.environ['PERF_EXEC_PATH'] + \
'/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
from perf_trace_context import perf_set_itrace_options, \
perf_sample_insn, perf_sample_srccode
try:
broken_pipe_exception = BrokenPipeError
except:
broken_pipe_exception = IOError
glb_switch_str = {}
glb_insn = False
glb_disassembler = None
glb_src = False
glb_source_file_name = None
glb_line_number = None
glb_dso = None
glb_stash_dict = {}
glb_output = None
glb_output_pos = 0
glb_cpu = -1
glb_time = 0
def get_optional_null(perf_dict, field):
if field in perf_dict:
return perf_dict[field]
return ""
def get_optional_zero(perf_dict, field):
if field in perf_dict:
return perf_dict[field]
return 0
def get_optional_bytes(perf_dict, field):
if field in perf_dict:
return perf_dict[field]
return bytes()
def get_optional(perf_dict, field):
if field in perf_dict:
return perf_dict[field]
return "[unknown]"
def get_offset(perf_dict, field):
if field in perf_dict:
return "+%#x" % perf_dict[field]
return ""
def trace_begin():
ap = argparse.ArgumentParser(usage = "", add_help = False)
ap.add_argument("--insn-trace", action='store_true')
ap.add_argument("--src-trace", action='store_true')
ap.add_argument("--all-switch-events", action='store_true')
ap.add_argument("--interleave", type=int, nargs='?', const=4, default=0)
global glb_args
global glb_insn
global glb_src
glb_args = ap.parse_args()
if glb_args.insn_trace:
print("Intel PT Instruction Trace")
itrace = "i0nsepwxI"
glb_insn = True
elif glb_args.src_trace:
print("Intel PT Source Trace")
itrace = "i0nsepwxI"
glb_insn = True
glb_src = True
else:
print("Intel PT Branch Trace, Power Events, Event Trace and PTWRITE")
itrace = "bepwxI"
global glb_disassembler
try:
glb_disassembler = LibXED()
except:
glb_disassembler = None
perf_set_itrace_options(perf_script_context, itrace)
def trace_end():
if glb_args.interleave:
flush_stashed_output()
print("End")
def trace_unhandled(event_name, context, event_fields_dict):
print(' '.join(['%s=%s'%(k,str(v))for k,v in sorted(event_fields_dict.items())]))
def stash_output():
global glb_stash_dict
global glb_output_pos
output_str = glb_output.getvalue()[glb_output_pos:]
n = len(output_str)
if n:
glb_output_pos += n
if glb_cpu not in glb_stash_dict:
glb_stash_dict[glb_cpu] = []
glb_stash_dict[glb_cpu].append(output_str)
def flush_stashed_output():
global glb_stash_dict
while glb_stash_dict:
cpus = list(glb_stash_dict.keys())
# Output at most glb_args.interleave output strings per cpu
for cpu in cpus:
items = glb_stash_dict[cpu]
countdown = glb_args.interleave
while len(items) and countdown:
sys.stdout.write(items[0])
del items[0]
countdown -= 1
if not items:
del glb_stash_dict[cpu]
def print_ptwrite(raw_buf):
data = struct.unpack_from("<IQ", raw_buf)
flags = data[0]
payload = data[1]
exact_ip = flags & 1
try:
s = payload.to_bytes(8, "little").decode("ascii").rstrip("\x00")
if not s.isprintable():
s = ""
except:
s = ""
print("IP: %u payload: %#x" % (exact_ip, payload), s, end=' ')
def print_cbr(raw_buf):
data = struct.unpack_from("<BBBBII", raw_buf)
cbr = data[0]
f = (data[4] + 500) / 1000
p = ((cbr * 1000 / data[2]) + 5) / 10
print("%3u freq: %4u MHz (%3u%%)" % (cbr, f, p), end=' ')
def print_mwait(raw_buf):
data = struct.unpack_from("<IQ", raw_buf)
payload = data[1]
hints = payload & 0xff
extensions = (payload >> 32) & 0x3
print("hints: %#x extensions: %#x" % (hints, extensions), end=' ')
def print_pwre(raw_buf):
data = struct.unpack_from("<IQ", raw_buf)
payload = data[1]
hw = (payload >> 7) & 1
cstate = (payload >> 12) & 0xf
subcstate = (payload >> 8) & 0xf
print("hw: %u cstate: %u sub-cstate: %u" % (hw, cstate, subcstate),
end=' ')
def print_exstop(raw_buf):
data = struct.unpack_from("<I", raw_buf)
flags = data[0]
exact_ip = flags & 1
print("IP: %u" % (exact_ip), end=' ')
def print_pwrx(raw_buf):
data = struct.unpack_from("<IQ", raw_buf)
payload = data[1]
deepest_cstate = payload & 0xf
last_cstate = (payload >> 4) & 0xf
wake_reason = (payload >> 8) & 0xf
print("deepest cstate: %u last cstate: %u wake reason: %#x" %
(deepest_cstate, last_cstate, wake_reason), end=' ')
def print_psb(raw_buf):
data = struct.unpack_from("<IQ", raw_buf)
offset = data[1]
print("offset: %#x" % (offset), end=' ')
glb_cfe = ["", "INTR", "IRET", "SMI", "RSM", "SIPI", "INIT", "VMENTRY", "VMEXIT",
"VMEXIT_INTR", "SHUTDOWN", "", "UINT", "UIRET"] + [""] * 18
glb_evd = ["", "PFA", "VMXQ", "VMXR"] + [""] * 60
def print_evt(raw_buf):
data = struct.unpack_from("<BBH", raw_buf)
typ = data[0] & 0x1f
ip_flag = (data[0] & 0x80) >> 7
vector = data[1]
evd_cnt = data[2]
s = glb_cfe[typ]
if s:
print(" cfe: %s IP: %u vector: %u" % (s, ip_flag, vector), end=' ')
else:
print(" cfe: %u IP: %u vector: %u" % (typ, ip_flag, vector), end=' ')
pos = 4
for i in range(evd_cnt):
data = struct.unpack_from("<QQ", raw_buf)
et = data[0] & 0x3f
s = glb_evd[et]
if s:
print("%s: %#x" % (s, data[1]), end=' ')
else:
print("EVD_%u: %#x" % (et, data[1]), end=' ')
def print_iflag(raw_buf):
data = struct.unpack_from("<IQ", raw_buf)
iflag = data[0] & 1
old_iflag = iflag ^ 1
via_branch = data[0] & 2
branch_ip = data[1]
if via_branch:
s = "via"
else:
s = "non"
print("IFLAG: %u->%u %s branch" % (old_iflag, iflag, s), end=' ')
def common_start_str(comm, sample):
ts = sample["time"]
cpu = sample["cpu"]
pid = sample["pid"]
tid = sample["tid"]
if "machine_pid" in sample:
machine_pid = sample["machine_pid"]
vcpu = sample["vcpu"]
return "VM:%5d VCPU:%03d %16s %5u/%-5u [%03u] %9u.%09u " % (machine_pid, vcpu, comm, pid, tid, cpu, ts / 1000000000, ts %1000000000)
else:
return "%16s %5u/%-5u [%03u] %9u.%09u " % (comm, pid, tid, cpu, ts / 1000000000, ts %1000000000)
def print_common_start(comm, sample, name):
flags_disp = get_optional_null(sample, "flags_disp")
# Unused fields:
# period = sample["period"]
# phys_addr = sample["phys_addr"]
# weight = sample["weight"]
# transaction = sample["transaction"]
# cpumode = get_optional_zero(sample, "cpumode")
print(common_start_str(comm, sample) + "%8s %21s" % (name, flags_disp), end=' ')
def print_instructions_start(comm, sample):
if "x" in get_optional_null(sample, "flags"):
print(common_start_str(comm, sample) + "x", end=' ')
else:
print(common_start_str(comm, sample), end=' ')
def disassem(insn, ip):
inst = glb_disassembler.Instruction()
glb_disassembler.SetMode(inst, 0) # Assume 64-bit
buf = create_string_buffer(64)
buf.value = insn
return glb_disassembler.DisassembleOne(inst, addressof(buf), len(insn), ip)
def print_common_ip(param_dict, sample, symbol, dso):
ip = sample["ip"]
offs = get_offset(param_dict, "symoff")
if "cyc_cnt" in sample:
cyc_cnt = sample["cyc_cnt"]
insn_cnt = get_optional_zero(sample, "insn_cnt")
ipc_str = " IPC: %#.2f (%u/%u)" % (insn_cnt / cyc_cnt, insn_cnt, cyc_cnt)
else:
ipc_str = ""
if glb_insn and glb_disassembler is not None:
insn = perf_sample_insn(perf_script_context)
if insn and len(insn):
cnt, text = disassem(insn, ip)
byte_str = ("%x" % ip).rjust(16)
if sys.version_info.major >= 3:
for k in range(cnt):
byte_str += " %02x" % insn[k]
else:
for k in xrange(cnt):
byte_str += " %02x" % ord(insn[k])
print("%-40s %-30s" % (byte_str, text), end=' ')
print("%s%s (%s)" % (symbol, offs, dso), end=' ')
else:
print("%16x %s%s (%s)" % (ip, symbol, offs, dso), end=' ')
if "addr_correlates_sym" in sample:
addr = sample["addr"]
dso = get_optional(sample, "addr_dso")
symbol = get_optional(sample, "addr_symbol")
offs = get_offset(sample, "addr_symoff")
print("=> %x %s%s (%s)%s" % (addr, symbol, offs, dso, ipc_str))
else:
print(ipc_str)
def print_srccode(comm, param_dict, sample, symbol, dso, with_insn):
ip = sample["ip"]
if symbol == "[unknown]":
start_str = common_start_str(comm, sample) + ("%x" % ip).rjust(16).ljust(40)
else:
offs = get_offset(param_dict, "symoff")
start_str = common_start_str(comm, sample) + (symbol + offs).ljust(40)
if with_insn and glb_insn and glb_disassembler is not None:
insn = perf_sample_insn(perf_script_context)
if insn and len(insn):
cnt, text = disassem(insn, ip)
start_str += text.ljust(30)
global glb_source_file_name
global glb_line_number
global glb_dso
source_file_name, line_number, source_line = perf_sample_srccode(perf_script_context)
if source_file_name:
if glb_line_number == line_number and glb_source_file_name == source_file_name:
src_str = ""
else:
if len(source_file_name) > 40:
src_file = ("..." + source_file_name[-37:]) + " "
else:
src_file = source_file_name.ljust(41)
if source_line is None:
src_str = src_file + str(line_number).rjust(4) + " <source not found>"
else:
src_str = src_file + str(line_number).rjust(4) + " " + source_line
glb_dso = None
elif dso == glb_dso:
src_str = ""
else:
src_str = dso
glb_dso = dso
glb_line_number = line_number
glb_source_file_name = source_file_name
print(start_str, src_str)
def do_process_event(param_dict):
sample = param_dict["sample"]
raw_buf = param_dict["raw_buf"]
comm = param_dict["comm"]
name = param_dict["ev_name"]
# Unused fields:
# callchain = param_dict["callchain"]
# brstack = param_dict["brstack"]
# brstacksym = param_dict["brstacksym"]
# event_attr = param_dict["attr"]
# Symbol and dso info are not always resolved
dso = get_optional(param_dict, "dso")
symbol = get_optional(param_dict, "symbol")
cpu = sample["cpu"]
if cpu in glb_switch_str:
print(glb_switch_str[cpu])
del glb_switch_str[cpu]
if name.startswith("instructions"):
if glb_src:
print_srccode(comm, param_dict, sample, symbol, dso, True)
else:
print_instructions_start(comm, sample)
print_common_ip(param_dict, sample, symbol, dso)
elif name.startswith("branches"):
if glb_src:
print_srccode(comm, param_dict, sample, symbol, dso, False)
else:
print_common_start(comm, sample, name)
print_common_ip(param_dict, sample, symbol, dso)
elif name == "ptwrite":
print_common_start(comm, sample, name)
print_ptwrite(raw_buf)
print_common_ip(param_dict, sample, symbol, dso)
elif name == "cbr":
print_common_start(comm, sample, name)
print_cbr(raw_buf)
print_common_ip(param_dict, sample, symbol, dso)
elif name == "mwait":
print_common_start(comm, sample, name)
print_mwait(raw_buf)
print_common_ip(param_dict, sample, symbol, dso)
elif name == "pwre":
print_common_start(comm, sample, name)
print_pwre(raw_buf)
print_common_ip(param_dict, sample, symbol, dso)
elif name == "exstop":
print_common_start(comm, sample, name)
print_exstop(raw_buf)
print_common_ip(param_dict, sample, symbol, dso)
elif name == "pwrx":
print_common_start(comm, sample, name)
print_pwrx(raw_buf)
print_common_ip(param_dict, sample, symbol, dso)
elif name == "psb":
print_common_start(comm, sample, name)
print_psb(raw_buf)
print_common_ip(param_dict, sample, symbol, dso)
elif name == "evt":
print_common_start(comm, sample, name)
print_evt(raw_buf)
print_common_ip(param_dict, sample, symbol, dso)
elif name == "iflag":
print_common_start(comm, sample, name)
print_iflag(raw_buf)
print_common_ip(param_dict, sample, symbol, dso)
else:
print_common_start(comm, sample, name)
print_common_ip(param_dict, sample, symbol, dso)
def interleave_events(param_dict):
global glb_cpu
global glb_time
global glb_output
global glb_output_pos
sample = param_dict["sample"]
glb_cpu = sample["cpu"]
ts = sample["time"]
if glb_time != ts:
glb_time = ts
flush_stashed_output()
glb_output_pos = 0
with contextlib.redirect_stdout(io.StringIO()) as glb_output:
do_process_event(param_dict)
stash_output()
def process_event(param_dict):
try:
if glb_args.interleave:
interleave_events(param_dict)
else:
do_process_event(param_dict)
except broken_pipe_exception:
# Stop python printing broken pipe errors and traceback
sys.stdout = open(os.devnull, 'w')
sys.exit(1)
def auxtrace_error(typ, code, cpu, pid, tid, ip, ts, msg, cpumode, *x):
if glb_args.interleave:
flush_stashed_output()
if len(x) >= 2 and x[0]:
machine_pid = x[0]
vcpu = x[1]
else:
machine_pid = 0
vcpu = -1
try:
if machine_pid:
print("VM:%5d VCPU:%03d %16s %5u/%-5u [%03u] %9u.%09u error type %u code %u: %s ip 0x%16x" %
(machine_pid, vcpu, "Trace error", pid, tid, cpu, ts / 1000000000, ts %1000000000, typ, code, msg, ip))
else:
print("%16s %5u/%-5u [%03u] %9u.%09u error type %u code %u: %s ip 0x%16x" %
("Trace error", pid, tid, cpu, ts / 1000000000, ts %1000000000, typ, code, msg, ip))
except broken_pipe_exception:
# Stop python printing broken pipe errors and traceback
sys.stdout = open(os.devnull, 'w')
sys.exit(1)
def context_switch(ts, cpu, pid, tid, np_pid, np_tid, machine_pid, out, out_preempt, *x):
if glb_args.interleave:
flush_stashed_output()
if out:
out_str = "Switch out "
else:
out_str = "Switch In "
if out_preempt:
preempt_str = "preempt"
else:
preempt_str = ""
if len(x) >= 2 and x[0]:
machine_pid = x[0]
vcpu = x[1]
else:
vcpu = None;
if machine_pid == -1:
machine_str = ""
elif vcpu is None:
machine_str = "machine PID %d" % machine_pid
else:
machine_str = "machine PID %d VCPU %d" % (machine_pid, vcpu)
switch_str = "%16s %5d/%-5d [%03u] %9u.%09u %5d/%-5d %s %s" % \
(out_str, pid, tid, cpu, ts / 1000000000, ts %1000000000, np_pid, np_tid, machine_str, preempt_str)
if glb_args.all_switch_events:
print(switch_str)
else:
global glb_switch_str
glb_switch_str[cpu] = switch_str
@@ -0,0 +1,107 @@
#!/usr/bin/env python
# SPDX-License-Identifier: GPL-2.0
# libxed.py: Python wrapper for libxed.so
# Copyright (c) 2014-2021, Intel Corporation.
# To use Intel XED, libxed.so must be present. To build and install
# libxed.so:
# git clone https://github.com/intelxed/mbuild.git mbuild
# git clone https://github.com/intelxed/xed
# cd xed
# ./mfile.py --share
# sudo ./mfile.py --prefix=/usr/local install
# sudo ldconfig
#
import sys
from ctypes import CDLL, Structure, create_string_buffer, addressof, sizeof, \
c_void_p, c_bool, c_byte, c_char, c_int, c_uint, c_longlong, c_ulonglong
# XED Disassembler
class xed_state_t(Structure):
_fields_ = [
("mode", c_int),
("width", c_int)
]
class XEDInstruction():
def __init__(self, libxed):
# Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion
xedd_t = c_byte * 512
self.xedd = xedd_t()
self.xedp = addressof(self.xedd)
libxed.xed_decoded_inst_zero(self.xedp)
self.state = xed_state_t()
self.statep = addressof(self.state)
# Buffer for disassembled instruction text
self.buffer = create_string_buffer(256)
self.bufferp = addressof(self.buffer)
class LibXED():
def __init__(self):
try:
self.libxed = CDLL("libxed.so")
except:
self.libxed = None
if not self.libxed:
self.libxed = CDLL("/usr/local/lib/libxed.so")
self.xed_tables_init = self.libxed.xed_tables_init
self.xed_tables_init.restype = None
self.xed_tables_init.argtypes = []
self.xed_decoded_inst_zero = self.libxed.xed_decoded_inst_zero
self.xed_decoded_inst_zero.restype = None
self.xed_decoded_inst_zero.argtypes = [ c_void_p ]
self.xed_operand_values_set_mode = self.libxed.xed_operand_values_set_mode
self.xed_operand_values_set_mode.restype = None
self.xed_operand_values_set_mode.argtypes = [ c_void_p, c_void_p ]
self.xed_decoded_inst_zero_keep_mode = self.libxed.xed_decoded_inst_zero_keep_mode
self.xed_decoded_inst_zero_keep_mode.restype = None
self.xed_decoded_inst_zero_keep_mode.argtypes = [ c_void_p ]
self.xed_decode = self.libxed.xed_decode
self.xed_decode.restype = c_int
self.xed_decode.argtypes = [ c_void_p, c_void_p, c_uint ]
self.xed_format_context = self.libxed.xed_format_context
self.xed_format_context.restype = c_uint
self.xed_format_context.argtypes = [ c_int, c_void_p, c_void_p, c_int, c_ulonglong, c_void_p, c_void_p ]
self.xed_tables_init()
def Instruction(self):
return XEDInstruction(self)
def SetMode(self, inst, mode):
if mode:
inst.state.mode = 4 # 32-bit
inst.state.width = 4 # 4 bytes
else:
inst.state.mode = 1 # 64-bit
inst.state.width = 8 # 8 bytes
self.xed_operand_values_set_mode(inst.xedp, inst.statep)
def DisassembleOne(self, inst, bytes_ptr, bytes_cnt, ip):
self.xed_decoded_inst_zero_keep_mode(inst.xedp)
err = self.xed_decode(inst.xedp, bytes_ptr, bytes_cnt)
if err:
return 0, ""
# Use AT&T mode (2), alternative is Intel (3)
ok = self.xed_format_context(2, inst.xedp, inst.bufferp, sizeof(inst.buffer), ip, 0, 0)
if not ok:
return 0, ""
if sys.version_info[0] == 2:
result = inst.buffer.value
else:
result = inst.buffer.value.decode()
# Return instruction length and the disassembled instruction text
# For now, assume the length is in byte 166
return inst.xedd[166], result
@@ -0,0 +1,127 @@
# mem-phys-addr.py: Resolve physical address samples
# SPDX-License-Identifier: GPL-2.0
#
# Copyright (c) 2018, Intel Corporation.
import os
import sys
import re
import bisect
import collections
from dataclasses import dataclass
from typing import (Dict, Optional)
sys.path.append(os.environ['PERF_EXEC_PATH'] + \
'/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
@dataclass(frozen=True)
class IomemEntry:
"""Read from a line in /proc/iomem"""
begin: int
end: int
indent: int
label: str
# Physical memory layout from /proc/iomem. Key is the indent and then
# a list of ranges.
iomem: Dict[int, list[IomemEntry]] = collections.defaultdict(list)
# Child nodes from the iomem parent.
children: Dict[IomemEntry, set[IomemEntry]] = collections.defaultdict(set)
# Maximum indent seen before an entry in the iomem file.
max_indent: int = 0
# Count for each range of memory.
load_mem_type_cnt: Dict[IomemEntry, int] = collections.Counter()
# Perf event name set from the first sample in the data.
event_name: Optional[str] = None
def parse_iomem():
"""Populate iomem from /proc/iomem file"""
global iomem
global max_indent
global children
with open('/proc/iomem', 'r', encoding='ascii') as f:
for line in f:
indent = 0
while line[indent] == ' ':
indent += 1
if indent > max_indent:
max_indent = indent
m = re.split('-|:', line, 2)
begin = int(m[0], 16)
end = int(m[1], 16)
label = m[2].strip()
entry = IomemEntry(begin, end, indent, label)
# Before adding entry, search for a parent node using its begin.
if indent > 0:
parent = find_memory_type(begin)
assert parent, f"Given indent expected a parent for {label}"
children[parent].add(entry)
iomem[indent].append(entry)
def find_memory_type(phys_addr) -> Optional[IomemEntry]:
"""Search iomem for the range containing phys_addr with the maximum indent"""
for i in range(max_indent, -1, -1):
if i not in iomem:
continue
position = bisect.bisect_right(iomem[i], phys_addr,
key=lambda entry: entry.begin)
if position is None:
continue
iomem_entry = iomem[i][position-1]
if iomem_entry.begin <= phys_addr <= iomem_entry.end:
return iomem_entry
print(f"Didn't find {phys_addr}")
return None
def print_memory_type():
print(f"Event: {event_name}")
print(f"{'Memory type':<40} {'count':>10} {'percentage':>10}")
print(f"{'-' * 40:<40} {'-' * 10:>10} {'-' * 10:>10}")
total = sum(load_mem_type_cnt.values())
# Add count from children into the parent.
for i in range(max_indent, -1, -1):
if i not in iomem:
continue
for entry in iomem[i]:
global children
for child in children[entry]:
if load_mem_type_cnt[child] > 0:
load_mem_type_cnt[entry] += load_mem_type_cnt[child]
def print_entries(entries):
"""Print counts from parents down to their children"""
global children
for entry in sorted(entries,
key = lambda entry: load_mem_type_cnt[entry],
reverse = True):
count = load_mem_type_cnt[entry]
if count > 0:
mem_type = ' ' * entry.indent + f"{entry.begin:x}-{entry.end:x} : {entry.label}"
percent = 100 * count / total
print(f"{mem_type:<40} {count:>10} {percent:>10.1f}")
print_entries(children[entry])
print_entries(iomem[0])
def trace_begin():
parse_iomem()
def trace_end():
print_memory_type()
def process_event(param_dict):
if "sample" not in param_dict:
return
sample = param_dict["sample"]
if "phys_addr" not in sample:
return
phys_addr = sample["phys_addr"]
entry = find_memory_type(phys_addr)
if entry:
load_mem_type_cnt[entry] += 1
global event_name
if event_name is None:
event_name = param_dict["ev_name"]
+78
View File
@@ -0,0 +1,78 @@
# Monitor the system for dropped packets and proudce a report of drop locations and counts
# SPDX-License-Identifier: GPL-2.0
from __future__ import print_function
import os
import sys
sys.path.append(os.environ['PERF_EXEC_PATH'] + \
'/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
from perf_trace_context import *
from Core import *
from Util import *
drop_log = {}
kallsyms = []
def get_kallsyms_table():
global kallsyms
try:
f = open("/proc/kallsyms", "r")
except:
return
for line in f:
loc = int(line.split()[0], 16)
name = line.split()[2]
kallsyms.append((loc, name))
kallsyms.sort()
def get_sym(sloc):
loc = int(sloc)
# Invariant: kallsyms[i][0] <= loc for all 0 <= i <= start
# kallsyms[i][0] > loc for all end <= i < len(kallsyms)
start, end = -1, len(kallsyms)
while end != start + 1:
pivot = (start + end) // 2
if loc < kallsyms[pivot][0]:
end = pivot
else:
start = pivot
# Now (start == -1 or kallsyms[start][0] <= loc)
# and (start == len(kallsyms) - 1 or loc < kallsyms[start + 1][0])
if start >= 0:
symloc, name = kallsyms[start]
return (name, loc - symloc)
else:
return (None, 0)
def print_drop_table():
print("%25s %25s %25s" % ("LOCATION", "OFFSET", "COUNT"))
for i in drop_log.keys():
(sym, off) = get_sym(i)
if sym == None:
sym = i
print("%25s %25s %25s" % (sym, off, drop_log[i]))
def trace_begin():
print("Starting trace (Ctrl-C to dump results)")
def trace_end():
print("Gathering kallsyms data")
get_kallsyms_table()
print_drop_table()
# called from perf, when it finds a corresponding event
def skb__kfree_skb(name, context, cpu, sec, nsec, pid, comm, callchain,
skbaddr, location, protocol, reason):
slocation = str(location)
try:
drop_log[slocation] = drop_log[slocation] + 1
except:
drop_log[slocation] = 1
@@ -0,0 +1,473 @@
# Display a process of packets and processed time.
# SPDX-License-Identifier: GPL-2.0
# It helps us to investigate networking or network device.
#
# options
# tx: show only tx chart
# rx: show only rx chart
# dev=: show only thing related to specified device
# debug: work with debug mode. It shows buffer status.
from __future__ import print_function
import os
import sys
sys.path.append(os.environ['PERF_EXEC_PATH'] + \
'/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
from perf_trace_context import *
from Core import *
from Util import *
from functools import cmp_to_key
all_event_list = []; # insert all tracepoint event related with this script
irq_dic = {}; # key is cpu and value is a list which stacks irqs
# which raise NET_RX softirq
net_rx_dic = {}; # key is cpu and value include time of NET_RX softirq-entry
# and a list which stacks receive
receive_hunk_list = []; # a list which include a sequence of receive events
rx_skb_list = []; # received packet list for matching
# skb_copy_datagram_iovec
buffer_budget = 65536; # the budget of rx_skb_list, tx_queue_list and
# tx_xmit_list
of_count_rx_skb_list = 0; # overflow count
tx_queue_list = []; # list of packets which pass through dev_queue_xmit
of_count_tx_queue_list = 0; # overflow count
tx_xmit_list = []; # list of packets which pass through dev_hard_start_xmit
of_count_tx_xmit_list = 0; # overflow count
tx_free_list = []; # list of packets which is freed
# options
show_tx = 0;
show_rx = 0;
dev = 0; # store a name of device specified by option "dev="
debug = 0;
# indices of event_info tuple
EINFO_IDX_NAME= 0
EINFO_IDX_CONTEXT=1
EINFO_IDX_CPU= 2
EINFO_IDX_TIME= 3
EINFO_IDX_PID= 4
EINFO_IDX_COMM= 5
# Calculate a time interval(msec) from src(nsec) to dst(nsec)
def diff_msec(src, dst):
return (dst - src) / 1000000.0
# Display a process of transmitting a packet
def print_transmit(hunk):
if dev != 0 and hunk['dev'].find(dev) < 0:
return
print("%7s %5d %6d.%06dsec %12.3fmsec %12.3fmsec" %
(hunk['dev'], hunk['len'],
nsecs_secs(hunk['queue_t']),
nsecs_nsecs(hunk['queue_t'])/1000,
diff_msec(hunk['queue_t'], hunk['xmit_t']),
diff_msec(hunk['xmit_t'], hunk['free_t'])))
# Format for displaying rx packet processing
PF_IRQ_ENTRY= " irq_entry(+%.3fmsec irq=%d:%s)"
PF_SOFT_ENTRY=" softirq_entry(+%.3fmsec)"
PF_NAPI_POLL= " napi_poll_exit(+%.3fmsec %s)"
PF_JOINT= " |"
PF_WJOINT= " | |"
PF_NET_RECV= " |---netif_receive_skb(+%.3fmsec skb=%x len=%d)"
PF_NET_RX= " |---netif_rx(+%.3fmsec skb=%x)"
PF_CPY_DGRAM= " | skb_copy_datagram_iovec(+%.3fmsec %d:%s)"
PF_KFREE_SKB= " | kfree_skb(+%.3fmsec location=%x)"
PF_CONS_SKB= " | consume_skb(+%.3fmsec)"
# Display a process of received packets and interrputs associated with
# a NET_RX softirq
def print_receive(hunk):
show_hunk = 0
irq_list = hunk['irq_list']
cpu = irq_list[0]['cpu']
base_t = irq_list[0]['irq_ent_t']
# check if this hunk should be showed
if dev != 0:
for i in range(len(irq_list)):
if irq_list[i]['name'].find(dev) >= 0:
show_hunk = 1
break
else:
show_hunk = 1
if show_hunk == 0:
return
print("%d.%06dsec cpu=%d" %
(nsecs_secs(base_t), nsecs_nsecs(base_t)/1000, cpu))
for i in range(len(irq_list)):
print(PF_IRQ_ENTRY %
(diff_msec(base_t, irq_list[i]['irq_ent_t']),
irq_list[i]['irq'], irq_list[i]['name']))
print(PF_JOINT)
irq_event_list = irq_list[i]['event_list']
for j in range(len(irq_event_list)):
irq_event = irq_event_list[j]
if irq_event['event'] == 'netif_rx':
print(PF_NET_RX %
(diff_msec(base_t, irq_event['time']),
irq_event['skbaddr']))
print(PF_JOINT)
print(PF_SOFT_ENTRY %
diff_msec(base_t, hunk['sirq_ent_t']))
print(PF_JOINT)
event_list = hunk['event_list']
for i in range(len(event_list)):
event = event_list[i]
if event['event_name'] == 'napi_poll':
print(PF_NAPI_POLL %
(diff_msec(base_t, event['event_t']),
event['dev']))
if i == len(event_list) - 1:
print("")
else:
print(PF_JOINT)
else:
print(PF_NET_RECV %
(diff_msec(base_t, event['event_t']),
event['skbaddr'],
event['len']))
if 'comm' in event.keys():
print(PF_WJOINT)
print(PF_CPY_DGRAM %
(diff_msec(base_t, event['comm_t']),
event['pid'], event['comm']))
elif 'handle' in event.keys():
print(PF_WJOINT)
if event['handle'] == "kfree_skb":
print(PF_KFREE_SKB %
(diff_msec(base_t,
event['comm_t']),
event['location']))
elif event['handle'] == "consume_skb":
print(PF_CONS_SKB %
diff_msec(base_t,
event['comm_t']))
print(PF_JOINT)
def trace_begin():
global show_tx
global show_rx
global dev
global debug
for i in range(len(sys.argv)):
if i == 0:
continue
arg = sys.argv[i]
if arg == 'tx':
show_tx = 1
elif arg =='rx':
show_rx = 1
elif arg.find('dev=',0, 4) >= 0:
dev = arg[4:]
elif arg == 'debug':
debug = 1
if show_tx == 0 and show_rx == 0:
show_tx = 1
show_rx = 1
def trace_end():
# order all events in time
all_event_list.sort(key=cmp_to_key(lambda a,b :a[EINFO_IDX_TIME] < b[EINFO_IDX_TIME]))
# process all events
for i in range(len(all_event_list)):
event_info = all_event_list[i]
name = event_info[EINFO_IDX_NAME]
if name == 'irq__softirq_exit':
handle_irq_softirq_exit(event_info)
elif name == 'irq__softirq_entry':
handle_irq_softirq_entry(event_info)
elif name == 'irq__softirq_raise':
handle_irq_softirq_raise(event_info)
elif name == 'irq__irq_handler_entry':
handle_irq_handler_entry(event_info)
elif name == 'irq__irq_handler_exit':
handle_irq_handler_exit(event_info)
elif name == 'napi__napi_poll':
handle_napi_poll(event_info)
elif name == 'net__netif_receive_skb':
handle_netif_receive_skb(event_info)
elif name == 'net__netif_rx':
handle_netif_rx(event_info)
elif name == 'skb__skb_copy_datagram_iovec':
handle_skb_copy_datagram_iovec(event_info)
elif name == 'net__net_dev_queue':
handle_net_dev_queue(event_info)
elif name == 'net__net_dev_xmit':
handle_net_dev_xmit(event_info)
elif name == 'skb__kfree_skb':
handle_kfree_skb(event_info)
elif name == 'skb__consume_skb':
handle_consume_skb(event_info)
# display receive hunks
if show_rx:
for i in range(len(receive_hunk_list)):
print_receive(receive_hunk_list[i])
# display transmit hunks
if show_tx:
print(" dev len Qdisc "
" netdevice free")
for i in range(len(tx_free_list)):
print_transmit(tx_free_list[i])
if debug:
print("debug buffer status")
print("----------------------------")
print("xmit Qdisc:remain:%d overflow:%d" %
(len(tx_queue_list), of_count_tx_queue_list))
print("xmit netdevice:remain:%d overflow:%d" %
(len(tx_xmit_list), of_count_tx_xmit_list))
print("receive:remain:%d overflow:%d" %
(len(rx_skb_list), of_count_rx_skb_list))
# called from perf, when it finds a correspoinding event
def irq__softirq_entry(name, context, cpu, sec, nsec, pid, comm, callchain, vec):
if symbol_str("irq__softirq_entry", "vec", vec) != "NET_RX":
return
event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, vec)
all_event_list.append(event_info)
def irq__softirq_exit(name, context, cpu, sec, nsec, pid, comm, callchain, vec):
if symbol_str("irq__softirq_entry", "vec", vec) != "NET_RX":
return
event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, vec)
all_event_list.append(event_info)
def irq__softirq_raise(name, context, cpu, sec, nsec, pid, comm, callchain, vec):
if symbol_str("irq__softirq_entry", "vec", vec) != "NET_RX":
return
event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, vec)
all_event_list.append(event_info)
def irq__irq_handler_entry(name, context, cpu, sec, nsec, pid, comm,
callchain, irq, irq_name):
event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm,
irq, irq_name)
all_event_list.append(event_info)
def irq__irq_handler_exit(name, context, cpu, sec, nsec, pid, comm, callchain, irq, ret):
event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, irq, ret)
all_event_list.append(event_info)
def napi__napi_poll(name, context, cpu, sec, nsec, pid, comm, callchain, napi,
dev_name, work=None, budget=None):
event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm,
napi, dev_name, work, budget)
all_event_list.append(event_info)
def net__netif_receive_skb(name, context, cpu, sec, nsec, pid, comm, callchain, skbaddr,
skblen, dev_name):
event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm,
skbaddr, skblen, dev_name)
all_event_list.append(event_info)
def net__netif_rx(name, context, cpu, sec, nsec, pid, comm, callchain, skbaddr,
skblen, dev_name):
event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm,
skbaddr, skblen, dev_name)
all_event_list.append(event_info)
def net__net_dev_queue(name, context, cpu, sec, nsec, pid, comm, callchain,
skbaddr, skblen, dev_name):
event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm,
skbaddr, skblen, dev_name)
all_event_list.append(event_info)
def net__net_dev_xmit(name, context, cpu, sec, nsec, pid, comm, callchain,
skbaddr, skblen, rc, dev_name):
event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm,
skbaddr, skblen, rc ,dev_name)
all_event_list.append(event_info)
def skb__kfree_skb(name, context, cpu, sec, nsec, pid, comm, callchain,
skbaddr, location, protocol, reason):
event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm,
skbaddr, location, protocol, reason)
all_event_list.append(event_info)
def skb__consume_skb(name, context, cpu, sec, nsec, pid, comm, callchain,
skbaddr, location):
event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm,
skbaddr)
all_event_list.append(event_info)
def skb__skb_copy_datagram_iovec(name, context, cpu, sec, nsec, pid, comm, callchain,
skbaddr, skblen):
event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm,
skbaddr, skblen)
all_event_list.append(event_info)
def handle_irq_handler_entry(event_info):
(name, context, cpu, time, pid, comm, irq, irq_name) = event_info
if cpu not in irq_dic.keys():
irq_dic[cpu] = []
irq_record = {'irq':irq, 'name':irq_name, 'cpu':cpu, 'irq_ent_t':time}
irq_dic[cpu].append(irq_record)
def handle_irq_handler_exit(event_info):
(name, context, cpu, time, pid, comm, irq, ret) = event_info
if cpu not in irq_dic.keys():
return
irq_record = irq_dic[cpu].pop()
if irq != irq_record['irq']:
return
irq_record.update({'irq_ext_t':time})
# if an irq doesn't include NET_RX softirq, drop.
if 'event_list' in irq_record.keys():
irq_dic[cpu].append(irq_record)
def handle_irq_softirq_raise(event_info):
(name, context, cpu, time, pid, comm, vec) = event_info
if cpu not in irq_dic.keys() \
or len(irq_dic[cpu]) == 0:
return
irq_record = irq_dic[cpu].pop()
if 'event_list' in irq_record.keys():
irq_event_list = irq_record['event_list']
else:
irq_event_list = []
irq_event_list.append({'time':time, 'event':'sirq_raise'})
irq_record.update({'event_list':irq_event_list})
irq_dic[cpu].append(irq_record)
def handle_irq_softirq_entry(event_info):
(name, context, cpu, time, pid, comm, vec) = event_info
net_rx_dic[cpu] = {'sirq_ent_t':time, 'event_list':[]}
def handle_irq_softirq_exit(event_info):
(name, context, cpu, time, pid, comm, vec) = event_info
irq_list = []
event_list = 0
if cpu in irq_dic.keys():
irq_list = irq_dic[cpu]
del irq_dic[cpu]
if cpu in net_rx_dic.keys():
sirq_ent_t = net_rx_dic[cpu]['sirq_ent_t']
event_list = net_rx_dic[cpu]['event_list']
del net_rx_dic[cpu]
if irq_list == [] or event_list == 0:
return
rec_data = {'sirq_ent_t':sirq_ent_t, 'sirq_ext_t':time,
'irq_list':irq_list, 'event_list':event_list}
# merge information related to a NET_RX softirq
receive_hunk_list.append(rec_data)
def handle_napi_poll(event_info):
(name, context, cpu, time, pid, comm, napi, dev_name,
work, budget) = event_info
if cpu in net_rx_dic.keys():
event_list = net_rx_dic[cpu]['event_list']
rec_data = {'event_name':'napi_poll',
'dev':dev_name, 'event_t':time,
'work':work, 'budget':budget}
event_list.append(rec_data)
def handle_netif_rx(event_info):
(name, context, cpu, time, pid, comm,
skbaddr, skblen, dev_name) = event_info
if cpu not in irq_dic.keys() \
or len(irq_dic[cpu]) == 0:
return
irq_record = irq_dic[cpu].pop()
if 'event_list' in irq_record.keys():
irq_event_list = irq_record['event_list']
else:
irq_event_list = []
irq_event_list.append({'time':time, 'event':'netif_rx',
'skbaddr':skbaddr, 'skblen':skblen, 'dev_name':dev_name})
irq_record.update({'event_list':irq_event_list})
irq_dic[cpu].append(irq_record)
def handle_netif_receive_skb(event_info):
global of_count_rx_skb_list
(name, context, cpu, time, pid, comm,
skbaddr, skblen, dev_name) = event_info
if cpu in net_rx_dic.keys():
rec_data = {'event_name':'netif_receive_skb',
'event_t':time, 'skbaddr':skbaddr, 'len':skblen}
event_list = net_rx_dic[cpu]['event_list']
event_list.append(rec_data)
rx_skb_list.insert(0, rec_data)
if len(rx_skb_list) > buffer_budget:
rx_skb_list.pop()
of_count_rx_skb_list += 1
def handle_net_dev_queue(event_info):
global of_count_tx_queue_list
(name, context, cpu, time, pid, comm,
skbaddr, skblen, dev_name) = event_info
skb = {'dev':dev_name, 'skbaddr':skbaddr, 'len':skblen, 'queue_t':time}
tx_queue_list.insert(0, skb)
if len(tx_queue_list) > buffer_budget:
tx_queue_list.pop()
of_count_tx_queue_list += 1
def handle_net_dev_xmit(event_info):
global of_count_tx_xmit_list
(name, context, cpu, time, pid, comm,
skbaddr, skblen, rc, dev_name) = event_info
if rc == 0: # NETDEV_TX_OK
for i in range(len(tx_queue_list)):
skb = tx_queue_list[i]
if skb['skbaddr'] == skbaddr:
skb['xmit_t'] = time
tx_xmit_list.insert(0, skb)
del tx_queue_list[i]
if len(tx_xmit_list) > buffer_budget:
tx_xmit_list.pop()
of_count_tx_xmit_list += 1
return
def handle_kfree_skb(event_info):
(name, context, cpu, time, pid, comm,
skbaddr, location, protocol, reason) = event_info
for i in range(len(tx_queue_list)):
skb = tx_queue_list[i]
if skb['skbaddr'] == skbaddr:
del tx_queue_list[i]
return
for i in range(len(tx_xmit_list)):
skb = tx_xmit_list[i]
if skb['skbaddr'] == skbaddr:
skb['free_t'] = time
tx_free_list.append(skb)
del tx_xmit_list[i]
return
for i in range(len(rx_skb_list)):
rec_data = rx_skb_list[i]
if rec_data['skbaddr'] == skbaddr:
rec_data.update({'handle':"kfree_skb",
'comm':comm, 'pid':pid, 'comm_t':time})
del rx_skb_list[i]
return
def handle_consume_skb(event_info):
(name, context, cpu, time, pid, comm, skbaddr) = event_info
for i in range(len(tx_xmit_list)):
skb = tx_xmit_list[i]
if skb['skbaddr'] == skbaddr:
skb['free_t'] = time
tx_free_list.append(skb)
del tx_xmit_list[i]
return
def handle_skb_copy_datagram_iovec(event_info):
(name, context, cpu, time, pid, comm, skbaddr, skblen) = event_info
for i in range(len(rx_skb_list)):
rec_data = rx_skb_list[i]
if skbaddr == rec_data['skbaddr']:
rec_data.update({'handle':"skb_copy_datagram_iovec",
'comm':comm, 'pid':pid, 'comm_t':time})
del rx_skb_list[i]
return
+989
View File
@@ -0,0 +1,989 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
#
# Run a perf script command multiple times in parallel, using perf script
# options --cpu and --time so that each job processes a different chunk
# of the data.
#
# Copyright (c) 2024, Intel Corporation.
import subprocess
import argparse
import pathlib
import shlex
import time
import copy
import sys
import os
import re
glb_prog_name = "parallel-perf.py"
glb_min_interval = 10.0
glb_min_samples = 64
class Verbosity():
def __init__(self, quiet=False, verbose=False, debug=False):
self.normal = True
self.verbose = verbose
self.debug = debug
self.self_test = True
if self.debug:
self.verbose = True
if self.verbose:
quiet = False
if quiet:
self.normal = False
# Manage work (Start/Wait/Kill), as represented by a subprocess.Popen command
class Work():
def __init__(self, cmd, pipe_to, output_dir="."):
self.popen = None
self.consumer = None
self.cmd = cmd
self.pipe_to = pipe_to
self.output_dir = output_dir
self.cmdout_name = f"{output_dir}/cmd.txt"
self.stdout_name = f"{output_dir}/out.txt"
self.stderr_name = f"{output_dir}/err.txt"
def Command(self):
sh_cmd = [ shlex.quote(x) for x in self.cmd ]
return " ".join(self.cmd)
def Stdout(self):
return open(self.stdout_name, "w")
def Stderr(self):
return open(self.stderr_name, "w")
def CreateOutputDir(self):
pathlib.Path(self.output_dir).mkdir(parents=True, exist_ok=True)
def Start(self):
if self.popen:
return
self.CreateOutputDir()
with open(self.cmdout_name, "w") as f:
f.write(self.Command())
f.write("\n")
stdout = self.Stdout()
stderr = self.Stderr()
if self.pipe_to:
self.popen = subprocess.Popen(self.cmd, stdout=subprocess.PIPE, stderr=stderr)
args = shlex.split(self.pipe_to)
self.consumer = subprocess.Popen(args, stdin=self.popen.stdout, stdout=stdout, stderr=stderr)
else:
self.popen = subprocess.Popen(self.cmd, stdout=stdout, stderr=stderr)
def RemoveEmptyErrFile(self):
if os.path.exists(self.stderr_name):
if os.path.getsize(self.stderr_name) == 0:
os.unlink(self.stderr_name)
def Errors(self):
if os.path.exists(self.stderr_name):
if os.path.getsize(self.stderr_name) != 0:
return [ f"Non-empty error file {self.stderr_name}" ]
return []
def TidyUp(self):
self.RemoveEmptyErrFile()
def RawPollWait(self, p, wait):
if wait:
return p.wait()
return p.poll()
def Poll(self, wait=False):
if not self.popen:
return None
result = self.RawPollWait(self.popen, wait)
if self.consumer:
res = result
result = self.RawPollWait(self.consumer, wait)
if result != None and res == None:
self.popen.kill()
result = None
elif result == 0 and res != None and res != 0:
result = res
if result != None:
self.TidyUp()
return result
def Wait(self):
return self.Poll(wait=True)
def Kill(self):
if not self.popen:
return
self.popen.kill()
if self.consumer:
self.consumer.kill()
def KillWork(worklist, verbosity):
for w in worklist:
w.Kill()
for w in worklist:
w.Wait()
def NumberOfCPUs():
return os.sysconf("SC_NPROCESSORS_ONLN")
def NanoSecsToSecsStr(x):
if x == None:
return ""
x = str(x)
if len(x) < 10:
x = "0" * (10 - len(x)) + x
return x[:len(x) - 9] + "." + x[-9:]
def InsertOptionAfter(cmd, option, after):
try:
pos = cmd.index(after)
cmd.insert(pos + 1, option)
except:
cmd.append(option)
def CreateWorkList(cmd, pipe_to, output_dir, cpus, time_ranges_by_cpu):
max_len = len(str(cpus[-1]))
cpu_dir_fmt = f"cpu-%.{max_len}u"
worklist = []
pos = 0
for cpu in cpus:
if cpu >= 0:
cpu_dir = os.path.join(output_dir, cpu_dir_fmt % cpu)
cpu_option = f"--cpu={cpu}"
else:
cpu_dir = output_dir
cpu_option = None
tr_dir_fmt = "time-range"
if len(time_ranges_by_cpu) > 1:
time_ranges = time_ranges_by_cpu[pos]
tr_dir_fmt += f"-{pos}"
pos += 1
else:
time_ranges = time_ranges_by_cpu[0]
max_len = len(str(len(time_ranges)))
tr_dir_fmt += f"-%.{max_len}u"
i = 0
for r in time_ranges:
if r == [None, None]:
time_option = None
work_output_dir = cpu_dir
else:
time_option = "--time=" + NanoSecsToSecsStr(r[0]) + "," + NanoSecsToSecsStr(r[1])
work_output_dir = os.path.join(cpu_dir, tr_dir_fmt % i)
i += 1
work_cmd = list(cmd)
if time_option != None:
InsertOptionAfter(work_cmd, time_option, "script")
if cpu_option != None:
InsertOptionAfter(work_cmd, cpu_option, "script")
w = Work(work_cmd, pipe_to, work_output_dir)
worklist.append(w)
return worklist
def DoRunWork(worklist, nr_jobs, verbosity):
nr_to_do = len(worklist)
not_started = list(worklist)
running = []
done = []
chg = False
while True:
nr_done = len(done)
if chg and verbosity.normal:
nr_run = len(running)
print(f"\rThere are {nr_to_do} jobs: {nr_done} completed, {nr_run} running", flush=True, end=" ")
if verbosity.verbose:
print()
chg = False
if nr_done == nr_to_do:
break
while len(running) < nr_jobs and len(not_started):
w = not_started.pop(0)
running.append(w)
if verbosity.verbose:
print("Starting:", w.Command())
w.Start()
chg = True
if len(running):
time.sleep(0.1)
finished = []
not_finished = []
while len(running):
w = running.pop(0)
r = w.Poll()
if r == None:
not_finished.append(w)
continue
if r == 0:
if verbosity.verbose:
print("Finished:", w.Command())
finished.append(w)
chg = True
continue
if verbosity.normal and not verbosity.verbose:
print()
print("Job failed!\n return code:", r, "\n command: ", w.Command())
if w.pipe_to:
print(" piped to: ", w.pipe_to)
print("Killing outstanding jobs")
KillWork(not_finished, verbosity)
KillWork(running, verbosity)
return False
running = not_finished
done += finished
errorlist = []
for w in worklist:
errorlist += w.Errors()
if len(errorlist):
print("Errors:")
for e in errorlist:
print(e)
elif verbosity.normal:
print("\r"," "*50, "\rAll jobs finished successfully", flush=True)
return True
def RunWork(worklist, nr_jobs=NumberOfCPUs(), verbosity=Verbosity()):
try:
return DoRunWork(worklist, nr_jobs, verbosity)
except:
for w in worklist:
w.Kill()
raise
return True
def ReadHeader(perf, file_name):
return subprocess.Popen([perf, "script", "--header-only", "--input", file_name], stdout=subprocess.PIPE).stdout.read().decode("utf-8")
def ParseHeader(hdr):
result = {}
lines = hdr.split("\n")
for line in lines:
if ":" in line and line[0] == "#":
pos = line.index(":")
name = line[1:pos-1].strip()
value = line[pos+1:].strip()
if name in result:
orig_name = name
nr = 2
while True:
name = f"{orig_name} {nr}"
if name not in result:
break
nr += 1
result[name] = value
return result
def HeaderField(hdr_dict, hdr_fld):
if hdr_fld not in hdr_dict:
raise Exception(f"'{hdr_fld}' missing from header information")
return hdr_dict[hdr_fld]
# Represent the position of an option within a command string
# and provide the option value and/or remove the option
class OptPos():
def Init(self, opt_element=-1, value_element=-1, opt_pos=-1, value_pos=-1, error=None):
self.opt_element = opt_element # list element that contains option
self.value_element = value_element # list element that contains option value
self.opt_pos = opt_pos # string position of option
self.value_pos = value_pos # string position of value
self.error = error # error message string
def __init__(self, args, short_name, long_name, default=None):
self.args = list(args)
self.default = default
n = 2 + len(long_name)
m = len(short_name)
pos = -1
for opt in args:
pos += 1
if m and opt[:2] == f"-{short_name}":
if len(opt) == 2:
if pos + 1 < len(args):
self.Init(pos, pos + 1, 0, 0)
else:
self.Init(error = f"-{short_name} option missing value")
else:
self.Init(pos, pos, 0, 2)
return
if opt[:n] == f"--{long_name}":
if len(opt) == n:
if pos + 1 < len(args):
self.Init(pos, pos + 1, 0, 0)
else:
self.Init(error = f"--{long_name} option missing value")
elif opt[n] == "=":
self.Init(pos, pos, 0, n + 1)
else:
self.Init(error = f"--{long_name} option expected '='")
return
if m and opt[:1] == "-" and opt[:2] != "--" and short_name in opt:
ipos = opt.index(short_name)
if "-" in opt[1:]:
hpos = opt[1:].index("-")
if hpos < ipos:
continue
if ipos + 1 == len(opt):
if pos + 1 < len(args):
self.Init(pos, pos + 1, ipos, 0)
else:
self.Init(error = f"-{short_name} option missing value")
else:
self.Init(pos, pos, ipos, ipos + 1)
return
self.Init()
def Value(self):
if self.opt_element >= 0:
if self.opt_element != self.value_element:
return self.args[self.value_element]
else:
return self.args[self.value_element][self.value_pos:]
return self.default
def Remove(self, args):
if self.opt_element == -1:
return
if self.opt_element != self.value_element:
del args[self.value_element]
if self.opt_pos:
args[self.opt_element] = args[self.opt_element][:self.opt_pos]
else:
del args[self.opt_element]
def DetermineInputFileName(cmd):
p = OptPos(cmd, "i", "input", "perf.data")
if p.error:
raise Exception(f"perf command {p.error}")
file_name = p.Value()
if not os.path.exists(file_name):
raise Exception(f"perf command input file '{file_name}' not found")
return file_name
def ReadOption(args, short_name, long_name, err_prefix, remove=False):
p = OptPos(args, short_name, long_name)
if p.error:
raise Exception(f"{err_prefix}{p.error}")
value = p.Value()
if remove:
p.Remove(args)
return value
def ExtractOption(args, short_name, long_name, err_prefix):
return ReadOption(args, short_name, long_name, err_prefix, True)
def ReadPerfOption(args, short_name, long_name):
return ReadOption(args, short_name, long_name, "perf command ")
def ExtractPerfOption(args, short_name, long_name):
return ExtractOption(args, short_name, long_name, "perf command ")
def PerfDoubleQuickCommands(cmd, file_name):
cpu_str = ReadPerfOption(cmd, "C", "cpu")
time_str = ReadPerfOption(cmd, "", "time")
# Use double-quick sampling to determine trace data density
times_cmd = ["perf", "script", "--ns", "--input", file_name, "--itrace=qqi"]
if cpu_str != None and cpu_str != "":
times_cmd.append(f"--cpu={cpu_str}")
if time_str != None and time_str != "":
times_cmd.append(f"--time={time_str}")
cnts_cmd = list(times_cmd)
cnts_cmd.append("-Fcpu")
times_cmd.append("-Fcpu,time")
return cnts_cmd, times_cmd
class CPUTimeRange():
def __init__(self, cpu):
self.cpu = cpu
self.sample_cnt = 0
self.time_ranges = None
self.interval = 0
self.interval_remaining = 0
self.remaining = 0
self.tr_pos = 0
def CalcTimeRangesByCPU(line, cpu, cpu_time_ranges, max_time):
cpu_time_range = cpu_time_ranges[cpu]
cpu_time_range.remaining -= 1
cpu_time_range.interval_remaining -= 1
if cpu_time_range.remaining == 0:
cpu_time_range.time_ranges[cpu_time_range.tr_pos][1] = max_time
return
if cpu_time_range.interval_remaining == 0:
time = TimeVal(line[1][:-1], 0)
time_ranges = cpu_time_range.time_ranges
time_ranges[cpu_time_range.tr_pos][1] = time - 1
time_ranges.append([time, max_time])
cpu_time_range.tr_pos += 1
cpu_time_range.interval_remaining = cpu_time_range.interval
def CountSamplesByCPU(line, cpu, cpu_time_ranges):
try:
cpu_time_ranges[cpu].sample_cnt += 1
except:
print("exception")
print("cpu", cpu)
print("len(cpu_time_ranges)", len(cpu_time_ranges))
raise
def ProcessCommandOutputLines(cmd, per_cpu, fn, *x):
# Assume CPU number is at beginning of line and enclosed by []
pat = re.compile(r"\s*\[[0-9]+\]")
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
while True:
line = p.stdout.readline()
if line:
line = line.decode("utf-8")
if pat.match(line):
line = line.split()
if per_cpu:
# Assumes CPU number is enclosed by []
cpu = int(line[0][1:-1])
else:
cpu = 0
fn(line, cpu, *x)
else:
break
p.wait()
def IntersectTimeRanges(new_time_ranges, time_ranges):
pos = 0
new_pos = 0
# Can assume len(time_ranges) != 0 and len(new_time_ranges) != 0
# Note also, there *must* be at least one intersection.
while pos < len(time_ranges) and new_pos < len(new_time_ranges):
# new end < old start => no intersection, remove new
if new_time_ranges[new_pos][1] < time_ranges[pos][0]:
del new_time_ranges[new_pos]
continue
# new start > old end => no intersection, check next
if new_time_ranges[new_pos][0] > time_ranges[pos][1]:
pos += 1
if pos < len(time_ranges):
continue
# no next, so remove remaining
while new_pos < len(new_time_ranges):
del new_time_ranges[new_pos]
return
# Found an intersection
# new start < old start => adjust new start = old start
if new_time_ranges[new_pos][0] < time_ranges[pos][0]:
new_time_ranges[new_pos][0] = time_ranges[pos][0]
# new end > old end => keep the overlap, insert the remainder
if new_time_ranges[new_pos][1] > time_ranges[pos][1]:
r = [ time_ranges[pos][1] + 1, new_time_ranges[new_pos][1] ]
new_time_ranges[new_pos][1] = time_ranges[pos][1]
new_pos += 1
new_time_ranges.insert(new_pos, r)
continue
# new [start, end] is within old [start, end]
new_pos += 1
def SplitTimeRangesByTraceDataDensity(time_ranges, cpus, nr, cmd, file_name, per_cpu, min_size, min_interval, verbosity):
if verbosity.normal:
print("\rAnalyzing...", flush=True, end=" ")
if verbosity.verbose:
print()
cnts_cmd, times_cmd = PerfDoubleQuickCommands(cmd, file_name)
nr_cpus = cpus[-1] + 1 if per_cpu else 1
if per_cpu:
nr_cpus = cpus[-1] + 1
cpu_time_ranges = [ CPUTimeRange(cpu) for cpu in range(nr_cpus) ]
else:
nr_cpus = 1
cpu_time_ranges = [ CPUTimeRange(-1) ]
if verbosity.debug:
print("nr_cpus", nr_cpus)
print("cnts_cmd", cnts_cmd)
print("times_cmd", times_cmd)
# Count the number of "double quick" samples per CPU
ProcessCommandOutputLines(cnts_cmd, per_cpu, CountSamplesByCPU, cpu_time_ranges)
tot = 0
mx = 0
for cpu_time_range in cpu_time_ranges:
cnt = cpu_time_range.sample_cnt
tot += cnt
if cnt > mx:
mx = cnt
if verbosity.debug:
print("cpu:", cpu_time_range.cpu, "sample_cnt", cnt)
if min_size < 1:
min_size = 1
if mx < min_size:
# Too little data to be worth splitting
if verbosity.debug:
print("Too little data to split by time")
if nr == 0:
nr = 1
return [ SplitTimeRangesIntoN(time_ranges, nr, min_interval) ]
if nr:
divisor = nr
min_size = 1
else:
divisor = NumberOfCPUs()
interval = int(round(tot / divisor, 0))
if interval < min_size:
interval = min_size
if verbosity.debug:
print("divisor", divisor)
print("min_size", min_size)
print("interval", interval)
min_time = time_ranges[0][0]
max_time = time_ranges[-1][1]
for cpu_time_range in cpu_time_ranges:
cnt = cpu_time_range.sample_cnt
if cnt == 0:
cpu_time_range.time_ranges = copy.deepcopy(time_ranges)
continue
# Adjust target interval for CPU to give approximately equal interval sizes
# Determine number of intervals, rounding to nearest integer
n = int(round(cnt / interval, 0))
if n < 1:
n = 1
# Determine interval size, rounding up
d, m = divmod(cnt, n)
if m:
d += 1
cpu_time_range.interval = d
cpu_time_range.interval_remaining = d
cpu_time_range.remaining = cnt
# Init. time ranges for each CPU with the start time
cpu_time_range.time_ranges = [ [min_time, max_time] ]
# Set time ranges so that the same number of "double quick" samples
# will fall into each time range.
ProcessCommandOutputLines(times_cmd, per_cpu, CalcTimeRangesByCPU, cpu_time_ranges, max_time)
for cpu_time_range in cpu_time_ranges:
if cpu_time_range.sample_cnt:
IntersectTimeRanges(cpu_time_range.time_ranges, time_ranges)
return [cpu_time_ranges[cpu].time_ranges for cpu in cpus]
def SplitSingleTimeRangeIntoN(time_range, n):
if n <= 1:
return [time_range]
start = time_range[0]
end = time_range[1]
duration = int((end - start + 1) / n)
if duration < 1:
return [time_range]
time_ranges = []
for i in range(n):
time_ranges.append([start, start + duration - 1])
start += duration
time_ranges[-1][1] = end
return time_ranges
def TimeRangeDuration(r):
return r[1] - r[0] + 1
def TotalDuration(time_ranges):
duration = 0
for r in time_ranges:
duration += TimeRangeDuration(r)
return duration
def SplitTimeRangesByInterval(time_ranges, interval):
new_ranges = []
for r in time_ranges:
duration = TimeRangeDuration(r)
n = duration / interval
n = int(round(n, 0))
new_ranges += SplitSingleTimeRangeIntoN(r, n)
return new_ranges
def SplitTimeRangesIntoN(time_ranges, n, min_interval):
if n <= len(time_ranges):
return time_ranges
duration = TotalDuration(time_ranges)
interval = duration / n
if interval < min_interval:
interval = min_interval
return SplitTimeRangesByInterval(time_ranges, interval)
def RecombineTimeRanges(tr):
new_tr = copy.deepcopy(tr)
n = len(new_tr)
i = 1
while i < len(new_tr):
# if prev end + 1 == cur start, combine them
if new_tr[i - 1][1] + 1 == new_tr[i][0]:
new_tr[i][0] = new_tr[i - 1][0]
del new_tr[i - 1]
else:
i += 1
return new_tr
def OpenTimeRangeEnds(time_ranges, min_time, max_time):
if time_ranges[0][0] <= min_time:
time_ranges[0][0] = None
if time_ranges[-1][1] >= max_time:
time_ranges[-1][1] = None
def BadTimeStr(time_str):
raise Exception(f"perf command bad time option: '{time_str}'\nCheck also 'time of first sample' and 'time of last sample' in perf script --header-only")
def ValidateTimeRanges(time_ranges, time_str):
n = len(time_ranges)
for i in range(n):
start = time_ranges[i][0]
end = time_ranges[i][1]
if i != 0 and start <= time_ranges[i - 1][1]:
BadTimeStr(time_str)
if start > end:
BadTimeStr(time_str)
def TimeVal(s, dflt):
s = s.strip()
if s == "":
return dflt
a = s.split(".")
if len(a) > 2:
raise Exception(f"Bad time value'{s}'")
x = int(a[0])
if x < 0:
raise Exception("Negative time not allowed")
x *= 1000000000
if len(a) > 1:
x += int((a[1] + "000000000")[:9])
return x
def BadCPUStr(cpu_str):
raise Exception(f"perf command bad cpu option: '{cpu_str}'\nCheck also 'nrcpus avail' in perf script --header-only")
def ParseTimeStr(time_str, min_time, max_time):
if time_str == None or time_str == "":
return [[min_time, max_time]]
time_ranges = []
for r in time_str.split():
a = r.split(",")
if len(a) != 2:
BadTimeStr(time_str)
try:
start = TimeVal(a[0], min_time)
end = TimeVal(a[1], max_time)
except:
BadTimeStr(time_str)
time_ranges.append([start, end])
ValidateTimeRanges(time_ranges, time_str)
return time_ranges
def ParseCPUStr(cpu_str, nr_cpus):
if cpu_str == None or cpu_str == "":
return [-1]
cpus = []
for r in cpu_str.split(","):
a = r.split("-")
if len(a) < 1 or len(a) > 2:
BadCPUStr(cpu_str)
try:
start = int(a[0].strip())
if len(a) > 1:
end = int(a[1].strip())
else:
end = start
except:
BadCPUStr(cpu_str)
if start < 0 or end < 0 or end < start or end >= nr_cpus:
BadCPUStr(cpu_str)
cpus.extend(range(start, end + 1))
cpus = list(set(cpus)) # Remove duplicates
cpus.sort()
return cpus
class ParallelPerf():
def __init__(self, a):
for arg_name in vars(a):
setattr(self, arg_name, getattr(a, arg_name))
self.orig_nr = self.nr
self.orig_cmd = list(self.cmd)
self.perf = self.cmd[0]
if os.path.exists(self.output_dir):
raise Exception(f"Output '{self.output_dir}' already exists")
if self.jobs < 0 or self.nr < 0 or self.interval < 0:
raise Exception("Bad options (negative values): try -h option for help")
if self.nr != 0 and self.interval != 0:
raise Exception("Cannot specify number of time subdivisions and time interval")
if self.jobs == 0:
self.jobs = NumberOfCPUs()
if self.nr == 0 and self.interval == 0:
if self.per_cpu:
self.nr = 1
else:
self.nr = self.jobs
def Init(self):
if self.verbosity.debug:
print("cmd", self.cmd)
self.file_name = DetermineInputFileName(self.cmd)
self.hdr = ReadHeader(self.perf, self.file_name)
self.hdr_dict = ParseHeader(self.hdr)
self.cmd_line = HeaderField(self.hdr_dict, "cmdline")
def ExtractTimeInfo(self):
self.min_time = TimeVal(HeaderField(self.hdr_dict, "time of first sample"), 0)
self.max_time = TimeVal(HeaderField(self.hdr_dict, "time of last sample"), 0)
self.time_str = ExtractPerfOption(self.cmd, "", "time")
self.time_ranges = ParseTimeStr(self.time_str, self.min_time, self.max_time)
if self.verbosity.debug:
print("time_ranges", self.time_ranges)
def ExtractCPUInfo(self):
if self.per_cpu:
nr_cpus = int(HeaderField(self.hdr_dict, "nrcpus avail"))
self.cpu_str = ExtractPerfOption(self.cmd, "C", "cpu")
if self.cpu_str == None or self.cpu_str == "":
self.cpus = [ x for x in range(nr_cpus) ]
else:
self.cpus = ParseCPUStr(self.cpu_str, nr_cpus)
else:
self.cpu_str = None
self.cpus = [-1]
if self.verbosity.debug:
print("cpus", self.cpus)
def IsIntelPT(self):
return self.cmd_line.find("intel_pt") >= 0
def SplitTimeRanges(self):
if self.IsIntelPT() and self.interval == 0:
self.split_time_ranges_for_each_cpu = \
SplitTimeRangesByTraceDataDensity(self.time_ranges, self.cpus, self.orig_nr,
self.orig_cmd, self.file_name, self.per_cpu,
self.min_size, self.min_interval, self.verbosity)
elif self.nr:
self.split_time_ranges_for_each_cpu = [ SplitTimeRangesIntoN(self.time_ranges, self.nr, self.min_interval) ]
else:
self.split_time_ranges_for_each_cpu = [ SplitTimeRangesByInterval(self.time_ranges, self.interval) ]
def CheckTimeRanges(self):
for tr in self.split_time_ranges_for_each_cpu:
# Re-combined time ranges should be the same
new_tr = RecombineTimeRanges(tr)
if new_tr != self.time_ranges:
if self.verbosity.debug:
print("tr", tr)
print("new_tr", new_tr)
raise Exception("Self test failed!")
def OpenTimeRangeEnds(self):
for time_ranges in self.split_time_ranges_for_each_cpu:
OpenTimeRangeEnds(time_ranges, self.min_time, self.max_time)
def CreateWorkList(self):
self.worklist = CreateWorkList(self.cmd, self.pipe_to, self.output_dir, self.cpus, self.split_time_ranges_for_each_cpu)
def PerfDataRecordedPerCPU(self):
if "--per-thread" in self.cmd_line.split():
return False
return True
def DefaultToPerCPU(self):
# --no-per-cpu option takes precedence
if self.no_per_cpu:
return False
if not self.PerfDataRecordedPerCPU():
return False
# Default to per-cpu for Intel PT data that was recorded per-cpu,
# because decoding can be done for each CPU separately.
if self.IsIntelPT():
return True
return False
def Config(self):
self.Init()
self.ExtractTimeInfo()
if not self.per_cpu:
self.per_cpu = self.DefaultToPerCPU()
if self.verbosity.debug:
print("per_cpu", self.per_cpu)
self.ExtractCPUInfo()
self.SplitTimeRanges()
if self.verbosity.self_test:
self.CheckTimeRanges()
# Prefer open-ended time range to starting / ending with min_time / max_time resp.
self.OpenTimeRangeEnds()
self.CreateWorkList()
def Run(self):
if self.dry_run:
print(len(self.worklist),"jobs:")
for w in self.worklist:
print(w.Command())
return True
result = RunWork(self.worklist, self.jobs, verbosity=self.verbosity)
if self.verbosity.verbose:
print(glb_prog_name, "done")
return result
def RunParallelPerf(a):
pp = ParallelPerf(a)
pp.Config()
return pp.Run()
def Main(args):
ap = argparse.ArgumentParser(
prog=glb_prog_name, formatter_class = argparse.RawDescriptionHelpFormatter,
description =
"""
Run a perf script command multiple times in parallel, using perf script options
--cpu and --time so that each job processes a different chunk of the data.
""",
epilog =
"""
Follow the options by '--' and then the perf script command e.g.
$ perf record -a -- sleep 10
$ parallel-perf.py --nr=4 -- perf script --ns
All jobs finished successfully
$ tree parallel-perf-output/
parallel-perf-output/
├── time-range-0
│   ├── cmd.txt
│   └── out.txt
├── time-range-1
│   ├── cmd.txt
│   └── out.txt
├── time-range-2
│   ├── cmd.txt
│   └── out.txt
└── time-range-3
├── cmd.txt
└── out.txt
$ find parallel-perf-output -name cmd.txt | sort | xargs grep -H .
parallel-perf-output/time-range-0/cmd.txt:perf script --time=,9466.504461499 --ns
parallel-perf-output/time-range-1/cmd.txt:perf script --time=9466.504461500,9469.005396999 --ns
parallel-perf-output/time-range-2/cmd.txt:perf script --time=9469.005397000,9471.506332499 --ns
parallel-perf-output/time-range-3/cmd.txt:perf script --time=9471.506332500, --ns
Any perf script command can be used, including the use of perf script options
--dlfilter and --script, so that the benefit of running parallel jobs
naturally extends to them also.
If option --pipe-to is used, standard output is first piped through that
command. Beware, if the command fails (e.g. grep with no matches), it will be
considered a fatal error.
Final standard output is redirected to files named out.txt in separate
subdirectories under the output directory. Similarly, standard error is
written to files named err.txt. In addition, files named cmd.txt contain the
corresponding perf script command. After processing, err.txt files are removed
if they are empty.
If any job exits with a non-zero exit code, then all jobs are killed and no
more are started. A message is printed if any job results in a non-empty
err.txt file.
There is a separate output subdirectory for each time range. If the --per-cpu
option is used, these are further grouped under cpu-n subdirectories, e.g.
$ parallel-perf.py --per-cpu --nr=2 -- perf script --ns --cpu=0,1
All jobs finished successfully
$ tree parallel-perf-output
parallel-perf-output/
├── cpu-0
│   ├── time-range-0
│   │   ├── cmd.txt
│   │   └── out.txt
│   └── time-range-1
│   ├── cmd.txt
│   └── out.txt
└── cpu-1
├── time-range-0
│   ├── cmd.txt
│   └── out.txt
└── time-range-1
├── cmd.txt
└── out.txt
$ find parallel-perf-output -name cmd.txt | sort | xargs grep -H .
parallel-perf-output/cpu-0/time-range-0/cmd.txt:perf script --cpu=0 --time=,9469.005396999 --ns
parallel-perf-output/cpu-0/time-range-1/cmd.txt:perf script --cpu=0 --time=9469.005397000, --ns
parallel-perf-output/cpu-1/time-range-0/cmd.txt:perf script --cpu=1 --time=,9469.005396999 --ns
parallel-perf-output/cpu-1/time-range-1/cmd.txt:perf script --cpu=1 --time=9469.005397000, --ns
Subdivisions of time range, and cpus if the --per-cpu option is used, are
expressed by the --time and --cpu perf script options respectively. If the
supplied perf script command has a --time option, then that time range is
subdivided, otherwise the time range given by 'time of first sample' to
'time of last sample' is used (refer perf script --header-only). Similarly, the
supplied perf script command may provide a --cpu option, and only those CPUs
will be processed.
To prevent time intervals becoming too small, the --min-interval option can
be used.
Note there is special handling for processing Intel PT traces. If an interval is
not specified and the perf record command contained the intel_pt event, then the
time range will be subdivided in order to produce subdivisions that contain
approximately the same amount of trace data. That is accomplished by counting
double-quick (--itrace=qqi) samples, and choosing time ranges that encompass
approximately the same number of samples. In that case, time ranges may not be
the same for each CPU processed. For Intel PT, --per-cpu is the default, but
that can be overridden by --no-per-cpu. Note, for Intel PT, double-quick
decoding produces 1 sample for each PSB synchronization packet, which in turn
come after a certain number of bytes output, determined by psb_period (refer
perf Intel PT documentation). The minimum number of double-quick samples that
will define a time range can be set by the --min_size option, which defaults to
64.
""")
ap.add_argument("-o", "--output-dir", default="parallel-perf-output", help="output directory (default 'parallel-perf-output')")
ap.add_argument("-j", "--jobs", type=int, default=0, help="maximum number of jobs to run in parallel at one time (default is the number of CPUs)")
ap.add_argument("-n", "--nr", type=int, default=0, help="number of time subdivisions (default is the number of jobs)")
ap.add_argument("-i", "--interval", type=float, default=0, help="subdivide the time range using this time interval (in seconds e.g. 0.1 for a tenth of a second)")
ap.add_argument("-c", "--per-cpu", action="store_true", help="process data for each CPU in parallel")
ap.add_argument("-m", "--min-interval", type=float, default=glb_min_interval, help=f"minimum interval (default {glb_min_interval} seconds)")
ap.add_argument("-p", "--pipe-to", help="command to pipe output to (optional)")
ap.add_argument("-N", "--no-per-cpu", action="store_true", help="do not process data for each CPU in parallel")
ap.add_argument("-b", "--min_size", type=int, default=glb_min_samples, help="minimum data size (for Intel PT in PSBs)")
ap.add_argument("-D", "--dry-run", action="store_true", help="do not run any jobs, just show the perf script commands")
ap.add_argument("-q", "--quiet", action="store_true", help="do not print any messages except errors")
ap.add_argument("-v", "--verbose", action="store_true", help="print more messages")
ap.add_argument("-d", "--debug", action="store_true", help="print debugging messages")
cmd_line = list(args)
try:
split_pos = cmd_line.index("--")
cmd = cmd_line[split_pos + 1:]
args = cmd_line[:split_pos]
except:
cmd = None
args = cmd_line
a = ap.parse_args(args=args[1:])
a.cmd = cmd
a.verbosity = Verbosity(a.quiet, a.verbose, a.debug)
try:
if a.cmd == None:
if len(args) <= 1:
ap.print_help()
return True
raise Exception("Command line must contain '--' before perf command")
return RunParallelPerf(a)
except Exception as e:
print("Fatal error: ", str(e))
if a.debug:
raise
return False
if __name__ == "__main__":
if not Main(sys.argv):
sys.exit(1)
@@ -0,0 +1,202 @@
# SPDX-License-Identifier: GPL-2.0+
#
# Copyright (C) 2018 Ravi Bangoria, IBM Corporation
#
# Hypervisor call statisics
from __future__ import print_function
import os
import sys
sys.path.append(os.environ['PERF_EXEC_PATH'] + \
'/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
from perf_trace_context import *
from Core import *
from Util import *
# output: {
# opcode: {
# 'min': minimum time nsec
# 'max': maximum time nsec
# 'time': average time nsec
# 'cnt': counter
# } ...
# }
output = {}
# d_enter: {
# cpu: {
# opcode: nsec
# } ...
# }
d_enter = {}
hcall_table = {
4: 'H_REMOVE',
8: 'H_ENTER',
12: 'H_READ',
16: 'H_CLEAR_MOD',
20: 'H_CLEAR_REF',
24: 'H_PROTECT',
28: 'H_GET_TCE',
32: 'H_PUT_TCE',
36: 'H_SET_SPRG0',
40: 'H_SET_DABR',
44: 'H_PAGE_INIT',
48: 'H_SET_ASR',
52: 'H_ASR_ON',
56: 'H_ASR_OFF',
60: 'H_LOGICAL_CI_LOAD',
64: 'H_LOGICAL_CI_STORE',
68: 'H_LOGICAL_CACHE_LOAD',
72: 'H_LOGICAL_CACHE_STORE',
76: 'H_LOGICAL_ICBI',
80: 'H_LOGICAL_DCBF',
84: 'H_GET_TERM_CHAR',
88: 'H_PUT_TERM_CHAR',
92: 'H_REAL_TO_LOGICAL',
96: 'H_HYPERVISOR_DATA',
100: 'H_EOI',
104: 'H_CPPR',
108: 'H_IPI',
112: 'H_IPOLL',
116: 'H_XIRR',
120: 'H_MIGRATE_DMA',
124: 'H_PERFMON',
220: 'H_REGISTER_VPA',
224: 'H_CEDE',
228: 'H_CONFER',
232: 'H_PROD',
236: 'H_GET_PPP',
240: 'H_SET_PPP',
244: 'H_PURR',
248: 'H_PIC',
252: 'H_REG_CRQ',
256: 'H_FREE_CRQ',
260: 'H_VIO_SIGNAL',
264: 'H_SEND_CRQ',
272: 'H_COPY_RDMA',
276: 'H_REGISTER_LOGICAL_LAN',
280: 'H_FREE_LOGICAL_LAN',
284: 'H_ADD_LOGICAL_LAN_BUFFER',
288: 'H_SEND_LOGICAL_LAN',
292: 'H_BULK_REMOVE',
304: 'H_MULTICAST_CTRL',
308: 'H_SET_XDABR',
312: 'H_STUFF_TCE',
316: 'H_PUT_TCE_INDIRECT',
332: 'H_CHANGE_LOGICAL_LAN_MAC',
336: 'H_VTERM_PARTNER_INFO',
340: 'H_REGISTER_VTERM',
344: 'H_FREE_VTERM',
348: 'H_RESET_EVENTS',
352: 'H_ALLOC_RESOURCE',
356: 'H_FREE_RESOURCE',
360: 'H_MODIFY_QP',
364: 'H_QUERY_QP',
368: 'H_REREGISTER_PMR',
372: 'H_REGISTER_SMR',
376: 'H_QUERY_MR',
380: 'H_QUERY_MW',
384: 'H_QUERY_HCA',
388: 'H_QUERY_PORT',
392: 'H_MODIFY_PORT',
396: 'H_DEFINE_AQP1',
400: 'H_GET_TRACE_BUFFER',
404: 'H_DEFINE_AQP0',
408: 'H_RESIZE_MR',
412: 'H_ATTACH_MCQP',
416: 'H_DETACH_MCQP',
420: 'H_CREATE_RPT',
424: 'H_REMOVE_RPT',
428: 'H_REGISTER_RPAGES',
432: 'H_DISABLE_AND_GETC',
436: 'H_ERROR_DATA',
440: 'H_GET_HCA_INFO',
444: 'H_GET_PERF_COUNT',
448: 'H_MANAGE_TRACE',
468: 'H_FREE_LOGICAL_LAN_BUFFER',
472: 'H_POLL_PENDING',
484: 'H_QUERY_INT_STATE',
580: 'H_ILLAN_ATTRIBUTES',
592: 'H_MODIFY_HEA_QP',
596: 'H_QUERY_HEA_QP',
600: 'H_QUERY_HEA',
604: 'H_QUERY_HEA_PORT',
608: 'H_MODIFY_HEA_PORT',
612: 'H_REG_BCMC',
616: 'H_DEREG_BCMC',
620: 'H_REGISTER_HEA_RPAGES',
624: 'H_DISABLE_AND_GET_HEA',
628: 'H_GET_HEA_INFO',
632: 'H_ALLOC_HEA_RESOURCE',
644: 'H_ADD_CONN',
648: 'H_DEL_CONN',
664: 'H_JOIN',
676: 'H_VASI_STATE',
688: 'H_ENABLE_CRQ',
696: 'H_GET_EM_PARMS',
720: 'H_SET_MPP',
724: 'H_GET_MPP',
748: 'H_HOME_NODE_ASSOCIATIVITY',
756: 'H_BEST_ENERGY',
764: 'H_XIRR_X',
768: 'H_RANDOM',
772: 'H_COP',
788: 'H_GET_MPP_X',
796: 'H_SET_MODE',
61440: 'H_RTAS',
}
def hcall_table_lookup(opcode):
if (opcode in hcall_table):
return hcall_table[opcode]
else:
return opcode
print_ptrn = '%-28s%10s%10s%10s%10s'
def trace_end():
print(print_ptrn % ('hcall', 'count', 'min(ns)', 'max(ns)', 'avg(ns)'))
print('-' * 68)
for opcode in output:
h_name = hcall_table_lookup(opcode)
time = output[opcode]['time']
cnt = output[opcode]['cnt']
min_t = output[opcode]['min']
max_t = output[opcode]['max']
print(print_ptrn % (h_name, cnt, min_t, max_t, time//cnt))
def powerpc__hcall_exit(name, context, cpu, sec, nsec, pid, comm, callchain,
opcode, retval):
if (cpu in d_enter and opcode in d_enter[cpu]):
diff = nsecs(sec, nsec) - d_enter[cpu][opcode]
if (opcode in output):
output[opcode]['time'] += diff
output[opcode]['cnt'] += 1
if (output[opcode]['min'] > diff):
output[opcode]['min'] = diff
if (output[opcode]['max'] < diff):
output[opcode]['max'] = diff
else:
output[opcode] = {
'time': diff,
'cnt': 1,
'min': diff,
'max': diff,
}
del d_enter[cpu][opcode]
# else:
# print("Can't find matching hcall_enter event. Ignoring sample")
def powerpc__hcall_entry(event_name, context, cpu, sec, nsec, pid, comm,
callchain, opcode):
if (cpu in d_enter):
d_enter[cpu][opcode] = nsecs(sec, nsec)
else:
d_enter[cpu] = {opcode: nsecs(sec, nsec)}
@@ -0,0 +1,462 @@
# Cpu task migration overview toy
#
# Copyright (C) 2010 Frederic Weisbecker <fweisbec@gmail.com>
#
# perf script event handlers have been generated by perf script -g python
#
# This software is distributed under the terms of the GNU General
# Public License ("GPL") version 2 as published by the Free Software
# Foundation.
from __future__ import print_function
import os
import sys
from collections import defaultdict
try:
from UserList import UserList
except ImportError:
# Python 3: UserList moved to the collections package
from collections import UserList
sys.path.append(os.environ['PERF_EXEC_PATH'] + \
'/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
sys.path.append('scripts/python/Perf-Trace-Util/lib/Perf/Trace')
from perf_trace_context import *
from Core import *
from SchedGui import *
threads = { 0 : "idle"}
def thread_name(pid):
return "%s:%d" % (threads[pid], pid)
class RunqueueEventUnknown:
@staticmethod
def color():
return None
def __repr__(self):
return "unknown"
class RunqueueEventSleep:
@staticmethod
def color():
return (0, 0, 0xff)
def __init__(self, sleeper):
self.sleeper = sleeper
def __repr__(self):
return "%s gone to sleep" % thread_name(self.sleeper)
class RunqueueEventWakeup:
@staticmethod
def color():
return (0xff, 0xff, 0)
def __init__(self, wakee):
self.wakee = wakee
def __repr__(self):
return "%s woke up" % thread_name(self.wakee)
class RunqueueEventFork:
@staticmethod
def color():
return (0, 0xff, 0)
def __init__(self, child):
self.child = child
def __repr__(self):
return "new forked task %s" % thread_name(self.child)
class RunqueueMigrateIn:
@staticmethod
def color():
return (0, 0xf0, 0xff)
def __init__(self, new):
self.new = new
def __repr__(self):
return "task migrated in %s" % thread_name(self.new)
class RunqueueMigrateOut:
@staticmethod
def color():
return (0xff, 0, 0xff)
def __init__(self, old):
self.old = old
def __repr__(self):
return "task migrated out %s" % thread_name(self.old)
class RunqueueSnapshot:
def __init__(self, tasks = [0], event = RunqueueEventUnknown()):
self.tasks = tuple(tasks)
self.event = event
def sched_switch(self, prev, prev_state, next):
event = RunqueueEventUnknown()
if taskState(prev_state) == "R" and next in self.tasks \
and prev in self.tasks:
return self
if taskState(prev_state) != "R":
event = RunqueueEventSleep(prev)
next_tasks = list(self.tasks[:])
if prev in self.tasks:
if taskState(prev_state) != "R":
next_tasks.remove(prev)
elif taskState(prev_state) == "R":
next_tasks.append(prev)
if next not in next_tasks:
next_tasks.append(next)
return RunqueueSnapshot(next_tasks, event)
def migrate_out(self, old):
if old not in self.tasks:
return self
next_tasks = [task for task in self.tasks if task != old]
return RunqueueSnapshot(next_tasks, RunqueueMigrateOut(old))
def __migrate_in(self, new, event):
if new in self.tasks:
self.event = event
return self
next_tasks = self.tasks[:] + tuple([new])
return RunqueueSnapshot(next_tasks, event)
def migrate_in(self, new):
return self.__migrate_in(new, RunqueueMigrateIn(new))
def wake_up(self, new):
return self.__migrate_in(new, RunqueueEventWakeup(new))
def wake_up_new(self, new):
return self.__migrate_in(new, RunqueueEventFork(new))
def load(self):
""" Provide the number of tasks on the runqueue.
Don't count idle"""
return len(self.tasks) - 1
def __repr__(self):
ret = self.tasks.__repr__()
ret += self.origin_tostring()
return ret
class TimeSlice:
def __init__(self, start, prev):
self.start = start
self.prev = prev
self.end = start
# cpus that triggered the event
self.event_cpus = []
if prev is not None:
self.total_load = prev.total_load
self.rqs = prev.rqs.copy()
else:
self.rqs = defaultdict(RunqueueSnapshot)
self.total_load = 0
def __update_total_load(self, old_rq, new_rq):
diff = new_rq.load() - old_rq.load()
self.total_load += diff
def sched_switch(self, ts_list, prev, prev_state, next, cpu):
old_rq = self.prev.rqs[cpu]
new_rq = old_rq.sched_switch(prev, prev_state, next)
if old_rq is new_rq:
return
self.rqs[cpu] = new_rq
self.__update_total_load(old_rq, new_rq)
ts_list.append(self)
self.event_cpus = [cpu]
def migrate(self, ts_list, new, old_cpu, new_cpu):
if old_cpu == new_cpu:
return
old_rq = self.prev.rqs[old_cpu]
out_rq = old_rq.migrate_out(new)
self.rqs[old_cpu] = out_rq
self.__update_total_load(old_rq, out_rq)
new_rq = self.prev.rqs[new_cpu]
in_rq = new_rq.migrate_in(new)
self.rqs[new_cpu] = in_rq
self.__update_total_load(new_rq, in_rq)
ts_list.append(self)
if old_rq is not out_rq:
self.event_cpus.append(old_cpu)
self.event_cpus.append(new_cpu)
def wake_up(self, ts_list, pid, cpu, fork):
old_rq = self.prev.rqs[cpu]
if fork:
new_rq = old_rq.wake_up_new(pid)
else:
new_rq = old_rq.wake_up(pid)
if new_rq is old_rq:
return
self.rqs[cpu] = new_rq
self.__update_total_load(old_rq, new_rq)
ts_list.append(self)
self.event_cpus = [cpu]
def next(self, t):
self.end = t
return TimeSlice(t, self)
class TimeSliceList(UserList):
def __init__(self, arg = []):
self.data = arg
def get_time_slice(self, ts):
if len(self.data) == 0:
slice = TimeSlice(ts, TimeSlice(-1, None))
else:
slice = self.data[-1].next(ts)
return slice
def find_time_slice(self, ts):
start = 0
end = len(self.data)
found = -1
searching = True
while searching:
if start == end or start == end - 1:
searching = False
i = (end + start) / 2
if self.data[i].start <= ts and self.data[i].end >= ts:
found = i
end = i
continue
if self.data[i].end < ts:
start = i
elif self.data[i].start > ts:
end = i
return found
def set_root_win(self, win):
self.root_win = win
def mouse_down(self, cpu, t):
idx = self.find_time_slice(t)
if idx == -1:
return
ts = self[idx]
rq = ts.rqs[cpu]
raw = "CPU: %d\n" % cpu
raw += "Last event : %s\n" % rq.event.__repr__()
raw += "Timestamp : %d.%06d\n" % (ts.start / (10 ** 9), (ts.start % (10 ** 9)) / 1000)
raw += "Duration : %6d us\n" % ((ts.end - ts.start) / (10 ** 6))
raw += "Load = %d\n" % rq.load()
for t in rq.tasks:
raw += "%s \n" % thread_name(t)
self.root_win.update_summary(raw)
def update_rectangle_cpu(self, slice, cpu):
rq = slice.rqs[cpu]
if slice.total_load != 0:
load_rate = rq.load() / float(slice.total_load)
else:
load_rate = 0
red_power = int(0xff - (0xff * load_rate))
color = (0xff, red_power, red_power)
top_color = None
if cpu in slice.event_cpus:
top_color = rq.event.color()
self.root_win.paint_rectangle_zone(cpu, color, top_color, slice.start, slice.end)
def fill_zone(self, start, end):
i = self.find_time_slice(start)
if i == -1:
return
for i in range(i, len(self.data)):
timeslice = self.data[i]
if timeslice.start > end:
return
for cpu in timeslice.rqs:
self.update_rectangle_cpu(timeslice, cpu)
def interval(self):
if len(self.data) == 0:
return (0, 0)
return (self.data[0].start, self.data[-1].end)
def nr_rectangles(self):
last_ts = self.data[-1]
max_cpu = 0
for cpu in last_ts.rqs:
if cpu > max_cpu:
max_cpu = cpu
return max_cpu
class SchedEventProxy:
def __init__(self):
self.current_tsk = defaultdict(lambda : -1)
self.timeslices = TimeSliceList()
def sched_switch(self, headers, prev_comm, prev_pid, prev_prio, prev_state,
next_comm, next_pid, next_prio):
""" Ensure the task we sched out this cpu is really the one
we logged. Otherwise we may have missed traces """
on_cpu_task = self.current_tsk[headers.cpu]
if on_cpu_task != -1 and on_cpu_task != prev_pid:
print("Sched switch event rejected ts: %s cpu: %d prev: %s(%d) next: %s(%d)" % \
headers.ts_format(), headers.cpu, prev_comm, prev_pid, next_comm, next_pid)
threads[prev_pid] = prev_comm
threads[next_pid] = next_comm
self.current_tsk[headers.cpu] = next_pid
ts = self.timeslices.get_time_slice(headers.ts())
ts.sched_switch(self.timeslices, prev_pid, prev_state, next_pid, headers.cpu)
def migrate(self, headers, pid, prio, orig_cpu, dest_cpu):
ts = self.timeslices.get_time_slice(headers.ts())
ts.migrate(self.timeslices, pid, orig_cpu, dest_cpu)
def wake_up(self, headers, comm, pid, success, target_cpu, fork):
if success == 0:
return
ts = self.timeslices.get_time_slice(headers.ts())
ts.wake_up(self.timeslices, pid, target_cpu, fork)
def trace_begin():
global parser
parser = SchedEventProxy()
def trace_end():
app = wx.App(False)
timeslices = parser.timeslices
frame = RootFrame(timeslices, "Migration")
app.MainLoop()
def sched__sched_stat_runtime(event_name, context, common_cpu,
common_secs, common_nsecs, common_pid, common_comm,
common_callchain, comm, pid, runtime, vruntime):
pass
def sched__sched_stat_iowait(event_name, context, common_cpu,
common_secs, common_nsecs, common_pid, common_comm,
common_callchain, comm, pid, delay):
pass
def sched__sched_stat_sleep(event_name, context, common_cpu,
common_secs, common_nsecs, common_pid, common_comm,
common_callchain, comm, pid, delay):
pass
def sched__sched_stat_wait(event_name, context, common_cpu,
common_secs, common_nsecs, common_pid, common_comm,
common_callchain, comm, pid, delay):
pass
def sched__sched_process_fork(event_name, context, common_cpu,
common_secs, common_nsecs, common_pid, common_comm,
common_callchain, parent_comm, parent_pid, child_comm, child_pid):
pass
def sched__sched_process_wait(event_name, context, common_cpu,
common_secs, common_nsecs, common_pid, common_comm,
common_callchain, comm, pid, prio):
pass
def sched__sched_process_exit(event_name, context, common_cpu,
common_secs, common_nsecs, common_pid, common_comm,
common_callchain, comm, pid, prio):
pass
def sched__sched_process_free(event_name, context, common_cpu,
common_secs, common_nsecs, common_pid, common_comm,
common_callchain, comm, pid, prio):
pass
def sched__sched_migrate_task(event_name, context, common_cpu,
common_secs, common_nsecs, common_pid, common_comm,
common_callchain, comm, pid, prio, orig_cpu,
dest_cpu):
headers = EventHeaders(common_cpu, common_secs, common_nsecs,
common_pid, common_comm, common_callchain)
parser.migrate(headers, pid, prio, orig_cpu, dest_cpu)
def sched__sched_switch(event_name, context, common_cpu,
common_secs, common_nsecs, common_pid, common_comm, common_callchain,
prev_comm, prev_pid, prev_prio, prev_state,
next_comm, next_pid, next_prio):
headers = EventHeaders(common_cpu, common_secs, common_nsecs,
common_pid, common_comm, common_callchain)
parser.sched_switch(headers, prev_comm, prev_pid, prev_prio, prev_state,
next_comm, next_pid, next_prio)
def sched__sched_wakeup_new(event_name, context, common_cpu,
common_secs, common_nsecs, common_pid, common_comm,
common_callchain, comm, pid, prio, success,
target_cpu):
headers = EventHeaders(common_cpu, common_secs, common_nsecs,
common_pid, common_comm, common_callchain)
parser.wake_up(headers, comm, pid, success, target_cpu, 1)
def sched__sched_wakeup(event_name, context, common_cpu,
common_secs, common_nsecs, common_pid, common_comm,
common_callchain, comm, pid, prio, success,
target_cpu):
headers = EventHeaders(common_cpu, common_secs, common_nsecs,
common_pid, common_comm, common_callchain)
parser.wake_up(headers, comm, pid, success, target_cpu, 0)
def sched__sched_wait_task(event_name, context, common_cpu,
common_secs, common_nsecs, common_pid, common_comm,
common_callchain, comm, pid, prio):
pass
def sched__sched_kthread_stop_ret(event_name, context, common_cpu,
common_secs, common_nsecs, common_pid, common_comm,
common_callchain, ret):
pass
def sched__sched_kthread_stop(event_name, context, common_cpu,
common_secs, common_nsecs, common_pid, common_comm,
common_callchain, comm, pid):
pass
def trace_unhandled(event_name, context, event_fields_dict):
pass
@@ -0,0 +1,89 @@
# system call top
# (c) 2010, Tom Zanussi <tzanussi@gmail.com>
# Licensed under the terms of the GNU GPL License version 2
#
# Periodically displays system-wide system call totals, broken down by
# syscall. If a [comm] arg is specified, only syscalls called by
# [comm] are displayed. If an [interval] arg is specified, the display
# will be refreshed every [interval] seconds. The default interval is
# 3 seconds.
from __future__ import print_function
import os, sys, time
try:
import thread
except ImportError:
import _thread as thread
sys.path.append(os.environ['PERF_EXEC_PATH'] + \
'/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
from perf_trace_context import *
from Core import *
from Util import *
usage = "perf script -s sctop.py [comm] [interval]\n";
for_comm = None
default_interval = 3
interval = default_interval
if len(sys.argv) > 3:
sys.exit(usage)
if len(sys.argv) > 2:
for_comm = sys.argv[1]
interval = int(sys.argv[2])
elif len(sys.argv) > 1:
try:
interval = int(sys.argv[1])
except ValueError:
for_comm = sys.argv[1]
interval = default_interval
syscalls = autodict()
def trace_begin():
thread.start_new_thread(print_syscall_totals, (interval,))
pass
def raw_syscalls__sys_enter(event_name, context, common_cpu,
common_secs, common_nsecs, common_pid, common_comm,
common_callchain, id, args):
if for_comm is not None:
if common_comm != for_comm:
return
try:
syscalls[id] += 1
except TypeError:
syscalls[id] = 1
def syscalls__sys_enter(event_name, context, common_cpu,
common_secs, common_nsecs, common_pid, common_comm,
id, args):
raw_syscalls__sys_enter(**locals())
def print_syscall_totals(interval):
while 1:
clear_term()
if for_comm is not None:
print("\nsyscall events for %s:\n" % (for_comm))
else:
print("\nsyscall events:\n")
print("%-40s %10s" % ("event", "count"))
print("%-40s %10s" %
("----------------------------------------",
"----------"))
for id, val in sorted(syscalls.items(),
key = lambda kv: (kv[1], kv[0]),
reverse = True):
try:
print("%-40s %10d" % (syscall_name(id), val))
except TypeError:
pass
syscalls.clear()
time.sleep(interval)
+127
View File
@@ -0,0 +1,127 @@
# stackcollapse.py - format perf samples with one line per distinct call stack
# SPDX-License-Identifier: GPL-2.0
#
# This script's output has two space-separated fields. The first is a semicolon
# separated stack including the program name (from the "comm" field) and the
# function names from the call stack. The second is a count:
#
# swapper;start_kernel;rest_init;cpu_idle;default_idle;native_safe_halt 2
#
# The file is sorted according to the first field.
#
# Input may be created and processed using:
#
# perf record -a -g -F 99 sleep 60
# perf script report stackcollapse > out.stacks-folded
#
# (perf script record stackcollapse works too).
#
# Written by Paolo Bonzini <pbonzini@redhat.com>
# Based on Brendan Gregg's stackcollapse-perf.pl script.
from __future__ import print_function
import os
import sys
from collections import defaultdict
from optparse import OptionParser, make_option
sys.path.append(os.environ['PERF_EXEC_PATH'] + \
'/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
from perf_trace_context import *
from Core import *
from EventClass import *
# command line parsing
option_list = [
# formatting options for the bottom entry of the stack
make_option("--include-tid", dest="include_tid",
action="store_true", default=False,
help="include thread id in stack"),
make_option("--include-pid", dest="include_pid",
action="store_true", default=False,
help="include process id in stack"),
make_option("--no-comm", dest="include_comm",
action="store_false", default=True,
help="do not separate stacks according to comm"),
make_option("--tidy-java", dest="tidy_java",
action="store_true", default=False,
help="beautify Java signatures"),
make_option("--kernel", dest="annotate_kernel",
action="store_true", default=False,
help="annotate kernel functions with _[k]")
]
parser = OptionParser(option_list=option_list)
(opts, args) = parser.parse_args()
if len(args) != 0:
parser.error("unexpected command line argument")
if opts.include_tid and not opts.include_comm:
parser.error("requesting tid but not comm is invalid")
if opts.include_pid and not opts.include_comm:
parser.error("requesting pid but not comm is invalid")
# event handlers
lines = defaultdict(lambda: 0)
def process_event(param_dict):
def tidy_function_name(sym, dso):
if sym is None:
sym = '[unknown]'
sym = sym.replace(';', ':')
if opts.tidy_java:
# the original stackcollapse-perf.pl script gives the
# example of converting this:
# Lorg/mozilla/javascript/MemberBox;.<init>(Ljava/lang/reflect/Method;)V
# to this:
# org/mozilla/javascript/MemberBox:.init
sym = sym.replace('<', '')
sym = sym.replace('>', '')
if sym[0] == 'L' and sym.find('/'):
sym = sym[1:]
try:
sym = sym[:sym.index('(')]
except ValueError:
pass
if opts.annotate_kernel and dso == '[kernel.kallsyms]':
return sym + '_[k]'
else:
return sym
stack = list()
if 'callchain' in param_dict:
for entry in param_dict['callchain']:
entry.setdefault('sym', dict())
entry['sym'].setdefault('name', None)
entry.setdefault('dso', None)
stack.append(tidy_function_name(entry['sym']['name'],
entry['dso']))
else:
param_dict.setdefault('symbol', None)
param_dict.setdefault('dso', None)
stack.append(tidy_function_name(param_dict['symbol'],
param_dict['dso']))
if opts.include_comm:
comm = param_dict["comm"].replace(' ', '_')
sep = "-"
if opts.include_pid:
comm = comm + sep + str(param_dict['sample']['pid'])
sep = "/"
if opts.include_tid:
comm = comm + sep + str(param_dict['sample']['tid'])
stack.append(comm)
stack_string = ';'.join(reversed(stack))
lines[stack_string] = lines[stack_string] + 1
def trace_end():
list = sorted(lines)
for stack in list:
print("%s %d" % (stack, lines[stack]))
@@ -0,0 +1,79 @@
# SPDX-License-Identifier: GPL-2.0
from __future__ import print_function
data = {}
times = []
threads = []
cpus = []
def get_key(time, event, cpu, thread):
return "%d-%s-%d-%d" % (time, event, cpu, thread)
def store_key(time, cpu, thread):
if (time not in times):
times.append(time)
if (cpu not in cpus):
cpus.append(cpu)
if (thread not in threads):
threads.append(thread)
def store(time, event, cpu, thread, val, ena, run):
#print("event %s cpu %d, thread %d, time %d, val %d, ena %d, run %d" %
# (event, cpu, thread, time, val, ena, run))
store_key(time, cpu, thread)
key = get_key(time, event, cpu, thread)
data[key] = [ val, ena, run]
def get(time, event, cpu, thread):
key = get_key(time, event, cpu, thread)
return data[key][0]
def stat__cycles_k(cpu, thread, time, val, ena, run):
store(time, "cycles", cpu, thread, val, ena, run);
def stat__instructions_k(cpu, thread, time, val, ena, run):
store(time, "instructions", cpu, thread, val, ena, run);
def stat__cycles_u(cpu, thread, time, val, ena, run):
store(time, "cycles", cpu, thread, val, ena, run);
def stat__instructions_u(cpu, thread, time, val, ena, run):
store(time, "instructions", cpu, thread, val, ena, run);
def stat__cycles(cpu, thread, time, val, ena, run):
store(time, "cycles", cpu, thread, val, ena, run);
def stat__instructions(cpu, thread, time, val, ena, run):
store(time, "instructions", cpu, thread, val, ena, run);
def stat__interval(time):
for cpu in cpus:
for thread in threads:
cyc = get(time, "cycles", cpu, thread)
ins = get(time, "instructions", cpu, thread)
cpi = 0
if ins != 0:
cpi = cyc/float(ins)
print("%15f: cpu %d, thread %d -> cpi %f (%d/%d)" % (time/(float(1000000000)), cpu, thread, cpi, cyc, ins))
def trace_end():
pass
# XXX trace_end callback could be used as an alternative place
# to compute same values as in the script above:
#
# for time in times:
# for cpu in cpus:
# for thread in threads:
# cyc = get(time, "cycles", cpu, thread)
# ins = get(time, "instructions", cpu, thread)
#
# if ins != 0:
# cpi = cyc/float(ins)
#
# print("time %.9f, cpu %d, thread %d -> cpi %f" % (time/(float(1000000000)), cpu, thread, cpi))
@@ -0,0 +1,75 @@
# system call counts, by pid
# (c) 2010, Tom Zanussi <tzanussi@gmail.com>
# Licensed under the terms of the GNU GPL License version 2
#
# Displays system-wide system call totals, broken down by syscall.
# If a [comm] arg is specified, only syscalls called by [comm] are displayed.
from __future__ import print_function
import os, sys
sys.path.append(os.environ['PERF_EXEC_PATH'] + \
'/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
from perf_trace_context import *
from Core import *
from Util import syscall_name
usage = "perf script -s syscall-counts-by-pid.py [comm]\n";
for_comm = None
for_pid = None
if len(sys.argv) > 2:
sys.exit(usage)
if len(sys.argv) > 1:
try:
for_pid = int(sys.argv[1])
except:
for_comm = sys.argv[1]
syscalls = autodict()
def trace_begin():
print("Press control+C to stop and show the summary")
def trace_end():
print_syscall_totals()
def raw_syscalls__sys_enter(event_name, context, common_cpu,
common_secs, common_nsecs, common_pid, common_comm,
common_callchain, id, args):
if (for_comm and common_comm != for_comm) or \
(for_pid and common_pid != for_pid ):
return
try:
syscalls[common_comm][common_pid][id] += 1
except TypeError:
syscalls[common_comm][common_pid][id] = 1
def syscalls__sys_enter(event_name, context, common_cpu,
common_secs, common_nsecs, common_pid, common_comm,
id, args):
raw_syscalls__sys_enter(**locals())
def print_syscall_totals():
if for_comm is not None:
print("\nsyscall events for %s:\n" % (for_comm))
else:
print("\nsyscall events by comm/pid:\n")
print("%-40s %10s" % ("comm [pid]/syscalls", "count"))
print("%-40s %10s" % ("----------------------------------------",
"----------"))
comm_keys = syscalls.keys()
for comm in comm_keys:
pid_keys = syscalls[comm].keys()
for pid in pid_keys:
print("\n%s [%d]" % (comm, pid))
id_keys = syscalls[comm][pid].keys()
for id, val in sorted(syscalls[comm][pid].items(),
key = lambda kv: (kv[1], kv[0]), reverse = True):
print(" %-38s %10d" % (syscall_name(id), val))
@@ -0,0 +1,65 @@
# system call counts
# (c) 2010, Tom Zanussi <tzanussi@gmail.com>
# Licensed under the terms of the GNU GPL License version 2
#
# Displays system-wide system call totals, broken down by syscall.
# If a [comm] arg is specified, only syscalls called by [comm] are displayed.
from __future__ import print_function
import os
import sys
sys.path.append(os.environ['PERF_EXEC_PATH'] + \
'/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
from perf_trace_context import *
from Core import *
from Util import syscall_name
usage = "perf script -s syscall-counts.py [comm]\n";
for_comm = None
if len(sys.argv) > 2:
sys.exit(usage)
if len(sys.argv) > 1:
for_comm = sys.argv[1]
syscalls = autodict()
def trace_begin():
print("Press control+C to stop and show the summary")
def trace_end():
print_syscall_totals()
def raw_syscalls__sys_enter(event_name, context, common_cpu,
common_secs, common_nsecs, common_pid, common_comm,
common_callchain, id, args):
if for_comm is not None:
if common_comm != for_comm:
return
try:
syscalls[id] += 1
except TypeError:
syscalls[id] = 1
def syscalls__sys_enter(event_name, context, common_cpu,
common_secs, common_nsecs, common_pid, common_comm, id, args):
raw_syscalls__sys_enter(**locals())
def print_syscall_totals():
if for_comm is not None:
print("\nsyscall events for %s:\n" % (for_comm))
else:
print("\nsyscall events:\n")
print("%-40s %10s" % ("event", "count"))
print("%-40s %10s" % ("----------------------------------------",
"-----------"))
for id, val in sorted(syscalls.items(),
key = lambda kv: (kv[1], kv[0]), reverse = True):
print("%-40s %10d" % (syscall_name(id), val))
+934
View File
@@ -0,0 +1,934 @@
# task-analyzer.py - comprehensive perf tasks analysis
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2022, Hagen Paul Pfeifer <hagen@jauu.net>
# Licensed under the terms of the GNU GPL License version 2
#
# Usage:
#
# perf record -e sched:sched_switch -a -- sleep 10
# perf script report task-analyzer
#
from __future__ import print_function
import sys
import os
import string
import argparse
import decimal
sys.path.append(
os.environ["PERF_EXEC_PATH"] + "/scripts/python/Perf-Trace-Util/lib/Perf/Trace"
)
from perf_trace_context import *
from Core import *
# Definition of possible ASCII color codes
_COLORS = {
"grey": "\033[90m",
"red": "\033[91m",
"green": "\033[92m",
"yellow": "\033[93m",
"blue": "\033[94m",
"violet": "\033[95m",
"reset": "\033[0m",
}
# Columns will have a static size to align everything properly
# Support of 116 days of active update with nano precision
LEN_SWITCHED_IN = len("9999999.999999999") # 17
LEN_SWITCHED_OUT = len("9999999.999999999") # 17
LEN_CPU = len("000")
LEN_PID = len("maxvalue") # 8
LEN_TID = len("maxvalue") # 8
LEN_COMM = len("max-comms-length") # 16
LEN_RUNTIME = len("999999.999") # 10
# Support of 3.45 hours of timespans
LEN_OUT_IN = len("99999999999.999") # 15
LEN_OUT_OUT = len("99999999999.999") # 15
LEN_IN_IN = len("99999999999.999") # 15
LEN_IN_OUT = len("99999999999.999") # 15
# py2/py3 compatibility layer, see PEP469
try:
dict.iteritems
except AttributeError:
# py3
def itervalues(d):
return iter(d.values())
def iteritems(d):
return iter(d.items())
else:
# py2
def itervalues(d):
return d.itervalues()
def iteritems(d):
return d.iteritems()
def _check_color():
global _COLORS
"""user enforced no-color or if stdout is no tty we disable colors"""
if sys.stdout.isatty() and args.stdio_color != "never":
return
_COLORS = {
"grey": "",
"red": "",
"green": "",
"yellow": "",
"blue": "",
"violet": "",
"reset": "",
}
def _parse_args():
global args
parser = argparse.ArgumentParser(description="Analyze tasks behavior")
parser.add_argument(
"--time-limit",
default=[],
help=
"print tasks only in time[s] window e.g"
" --time-limit 123.111:789.222(print all between 123.111 and 789.222)"
" --time-limit 123: (print all from 123)"
" --time-limit :456 (print all until incl. 456)",
)
parser.add_argument(
"--summary", action="store_true", help="print addtional runtime information"
)
parser.add_argument(
"--summary-only", action="store_true", help="print only summary without traces"
)
parser.add_argument(
"--summary-extended",
action="store_true",
help="print the summary with additional information of max inter task times"
" relative to the prev task",
)
parser.add_argument(
"--ns", action="store_true", help="show timestamps in nanoseconds"
)
parser.add_argument(
"--ms", action="store_true", help="show timestamps in milliseconds"
)
parser.add_argument(
"--extended-times",
action="store_true",
help="Show the elapsed times between schedule in/schedule out"
" of this task and the schedule in/schedule out of previous occurrence"
" of the same task",
)
parser.add_argument(
"--filter-tasks",
default=[],
help="filter out unneeded tasks by tid, pid or processname."
" E.g --filter-task 1337,/sbin/init ",
)
parser.add_argument(
"--limit-to-tasks",
default=[],
help="limit output to selected task by tid, pid, processname."
" E.g --limit-to-tasks 1337,/sbin/init",
)
parser.add_argument(
"--highlight-tasks",
default="",
help="colorize special tasks by their pid/tid/comm."
" E.g. --highlight-tasks 1:red,mutt:yellow"
" Colors available: red,grey,yellow,blue,violet,green",
)
parser.add_argument(
"--rename-comms-by-tids",
default="",
help="rename task names by using tid (<tid>:<newname>,<tid>:<newname>)"
" This option is handy for inexpressive processnames like python interpreted"
" process. E.g --rename 1337:my-python-app",
)
parser.add_argument(
"--stdio-color",
default="auto",
choices=["always", "never", "auto"],
help="always, never or auto, allowing configuring color output"
" via the command line",
)
parser.add_argument(
"--csv",
default="",
help="Write trace to file selected by user. Options, like --ns or --extended"
"-times are used.",
)
parser.add_argument(
"--csv-summary",
default="",
help="Write summary to file selected by user. Options, like --ns or"
" --summary-extended are used.",
)
args = parser.parse_args()
args.tid_renames = dict()
_argument_filter_sanity_check()
_argument_prepare_check()
def time_uniter(unit):
picker = {
"s": 1,
"ms": 1e3,
"us": 1e6,
"ns": 1e9,
}
return picker[unit]
def _init_db():
global db
db = dict()
db["running"] = dict()
db["cpu"] = dict()
db["tid"] = dict()
db["global"] = []
if args.summary or args.summary_extended or args.summary_only:
db["task_info"] = dict()
db["runtime_info"] = dict()
# min values for summary depending on the header
db["task_info"]["pid"] = len("PID")
db["task_info"]["tid"] = len("TID")
db["task_info"]["comm"] = len("Comm")
db["runtime_info"]["runs"] = len("Runs")
db["runtime_info"]["acc"] = len("Accumulated")
db["runtime_info"]["max"] = len("Max")
db["runtime_info"]["max_at"] = len("Max At")
db["runtime_info"]["min"] = len("Min")
db["runtime_info"]["mean"] = len("Mean")
db["runtime_info"]["median"] = len("Median")
if args.summary_extended:
db["inter_times"] = dict()
db["inter_times"]["out_in"] = len("Out-In")
db["inter_times"]["inter_at"] = len("At")
db["inter_times"]["out_out"] = len("Out-Out")
db["inter_times"]["in_in"] = len("In-In")
db["inter_times"]["in_out"] = len("In-Out")
def _median(numbers):
"""phython3 hat statistics module - we have nothing"""
n = len(numbers)
index = n // 2
if n % 2:
return sorted(numbers)[index]
return sum(sorted(numbers)[index - 1 : index + 1]) / 2
def _mean(numbers):
return sum(numbers) / len(numbers)
class Timespans(object):
"""
The elapsed time between two occurrences of the same task is being tracked with the
help of this class. There are 4 of those Timespans Out-Out, In-Out, Out-In and
In-In.
The first half of the name signals the first time point of the
first task. The second half of the name represents the second
timepoint of the second task.
"""
def __init__(self):
self._last_start = None
self._last_finish = None
self.out_out = -1
self.in_out = -1
self.out_in = -1
self.in_in = -1
if args.summary_extended:
self._time_in = -1
self.max_out_in = -1
self.max_at = -1
self.max_in_out = -1
self.max_in_in = -1
self.max_out_out = -1
def feed(self, task):
"""
Called for every recorded trace event to find process pair and calculate the
task timespans. Chronological ordering, feed does not do reordering
"""
if not self._last_finish:
self._last_start = task.time_in(time_unit)
self._last_finish = task.time_out(time_unit)
return
self._time_in = task.time_in()
time_in = task.time_in(time_unit)
time_out = task.time_out(time_unit)
self.in_in = time_in - self._last_start
self.out_in = time_in - self._last_finish
self.in_out = time_out - self._last_start
self.out_out = time_out - self._last_finish
if args.summary_extended:
self._update_max_entries()
self._last_finish = task.time_out(time_unit)
self._last_start = task.time_in(time_unit)
def _update_max_entries(self):
if self.in_in > self.max_in_in:
self.max_in_in = self.in_in
if self.out_out > self.max_out_out:
self.max_out_out = self.out_out
if self.in_out > self.max_in_out:
self.max_in_out = self.in_out
if self.out_in > self.max_out_in:
self.max_out_in = self.out_in
self.max_at = self._time_in
class Summary(object):
"""
Primary instance for calculating the summary output. Processes the whole trace to
find and memorize relevant data such as mean, max et cetera. This instance handles
dynamic alignment aspects for summary output.
"""
def __init__(self):
self._body = []
class AlignmentHelper:
"""
Used to calculated the alignment for the output of the summary.
"""
def __init__(self, pid, tid, comm, runs, acc, mean,
median, min, max, max_at):
self.pid = pid
self.tid = tid
self.comm = comm
self.runs = runs
self.acc = acc
self.mean = mean
self.median = median
self.min = min
self.max = max
self.max_at = max_at
if args.summary_extended:
self.out_in = None
self.inter_at = None
self.out_out = None
self.in_in = None
self.in_out = None
def _print_header(self):
'''
Output is trimmed in _format_stats thus additional adjustment in the header
is needed, depending on the choice of timeunit. The adjustment corresponds
to the amount of column titles being adjusted in _column_titles.
'''
decimal_precision = 6 if not args.ns else 9
fmt = " {{:^{}}}".format(sum(db["task_info"].values()))
fmt += " {{:^{}}}".format(
sum(db["runtime_info"].values()) - 2 * decimal_precision
)
_header = ("Task Information", "Runtime Information")
if args.summary_extended:
fmt += " {{:^{}}}".format(
sum(db["inter_times"].values()) - 4 * decimal_precision
)
_header += ("Max Inter Task Times",)
fd_sum.write(fmt.format(*_header) + "\n")
def _column_titles(self):
"""
Cells are being processed and displayed in different way so an alignment adjust
is implemented depeding on the choice of the timeunit. The positions of the max
values are being displayed in grey. Thus in their format two additional {},
are placed for color set and reset.
"""
separator, fix_csv_align = _prepare_fmt_sep()
decimal_precision, time_precision = _prepare_fmt_precision()
fmt = "{{:>{}}}".format(db["task_info"]["pid"] * fix_csv_align)
fmt += "{}{{:>{}}}".format(separator, db["task_info"]["tid"] * fix_csv_align)
fmt += "{}{{:>{}}}".format(separator, db["task_info"]["comm"] * fix_csv_align)
fmt += "{}{{:>{}}}".format(separator, db["runtime_info"]["runs"] * fix_csv_align)
fmt += "{}{{:>{}}}".format(separator, db["runtime_info"]["acc"] * fix_csv_align)
fmt += "{}{{:>{}}}".format(separator, db["runtime_info"]["mean"] * fix_csv_align)
fmt += "{}{{:>{}}}".format(
separator, db["runtime_info"]["median"] * fix_csv_align
)
fmt += "{}{{:>{}}}".format(
separator, (db["runtime_info"]["min"] - decimal_precision) * fix_csv_align
)
fmt += "{}{{:>{}}}".format(
separator, (db["runtime_info"]["max"] - decimal_precision) * fix_csv_align
)
fmt += "{}{{}}{{:>{}}}{{}}".format(
separator, (db["runtime_info"]["max_at"] - time_precision) * fix_csv_align
)
column_titles = ("PID", "TID", "Comm")
column_titles += ("Runs", "Accumulated", "Mean", "Median", "Min", "Max")
column_titles += (_COLORS["grey"], "Max At", _COLORS["reset"])
if args.summary_extended:
fmt += "{}{{:>{}}}".format(
separator,
(db["inter_times"]["out_in"] - decimal_precision) * fix_csv_align
)
fmt += "{}{{}}{{:>{}}}{{}}".format(
separator,
(db["inter_times"]["inter_at"] - time_precision) * fix_csv_align
)
fmt += "{}{{:>{}}}".format(
separator,
(db["inter_times"]["out_out"] - decimal_precision) * fix_csv_align
)
fmt += "{}{{:>{}}}".format(
separator,
(db["inter_times"]["in_in"] - decimal_precision) * fix_csv_align
)
fmt += "{}{{:>{}}}".format(
separator,
(db["inter_times"]["in_out"] - decimal_precision) * fix_csv_align
)
column_titles += ("Out-In", _COLORS["grey"], "Max At", _COLORS["reset"],
"Out-Out", "In-In", "In-Out")
fd_sum.write(fmt.format(*column_titles) + "\n")
def _task_stats(self):
"""calculates the stats of every task and constructs the printable summary"""
for tid in sorted(db["tid"]):
color_one_sample = _COLORS["grey"]
color_reset = _COLORS["reset"]
no_executed = 0
runtimes = []
time_in = []
timespans = Timespans()
for task in db["tid"][tid]:
pid = task.pid
comm = task.comm
no_executed += 1
runtimes.append(task.runtime(time_unit))
time_in.append(task.time_in())
timespans.feed(task)
if len(runtimes) > 1:
color_one_sample = ""
color_reset = ""
time_max = max(runtimes)
time_min = min(runtimes)
max_at = time_in[runtimes.index(max(runtimes))]
# The size of the decimal after sum,mean and median varies, thus we cut
# the decimal number, by rounding it. It has no impact on the output,
# because we have a precision of the decimal points at the output.
time_sum = round(sum(runtimes), 3)
time_mean = round(_mean(runtimes), 3)
time_median = round(_median(runtimes), 3)
align_helper = self.AlignmentHelper(pid, tid, comm, no_executed, time_sum,
time_mean, time_median, time_min, time_max, max_at)
self._body.append([pid, tid, comm, no_executed, time_sum, color_one_sample,
time_mean, time_median, time_min, time_max,
_COLORS["grey"], max_at, _COLORS["reset"], color_reset])
if args.summary_extended:
self._body[-1].extend([timespans.max_out_in,
_COLORS["grey"], timespans.max_at,
_COLORS["reset"], timespans.max_out_out,
timespans.max_in_in,
timespans.max_in_out])
align_helper.out_in = timespans.max_out_in
align_helper.inter_at = timespans.max_at
align_helper.out_out = timespans.max_out_out
align_helper.in_in = timespans.max_in_in
align_helper.in_out = timespans.max_in_out
self._calc_alignments_summary(align_helper)
def _format_stats(self):
separator, fix_csv_align = _prepare_fmt_sep()
decimal_precision, time_precision = _prepare_fmt_precision()
len_pid = db["task_info"]["pid"] * fix_csv_align
len_tid = db["task_info"]["tid"] * fix_csv_align
len_comm = db["task_info"]["comm"] * fix_csv_align
len_runs = db["runtime_info"]["runs"] * fix_csv_align
len_acc = db["runtime_info"]["acc"] * fix_csv_align
len_mean = db["runtime_info"]["mean"] * fix_csv_align
len_median = db["runtime_info"]["median"] * fix_csv_align
len_min = (db["runtime_info"]["min"] - decimal_precision) * fix_csv_align
len_max = (db["runtime_info"]["max"] - decimal_precision) * fix_csv_align
len_max_at = (db["runtime_info"]["max_at"] - time_precision) * fix_csv_align
if args.summary_extended:
len_out_in = (
db["inter_times"]["out_in"] - decimal_precision
) * fix_csv_align
len_inter_at = (
db["inter_times"]["inter_at"] - time_precision
) * fix_csv_align
len_out_out = (
db["inter_times"]["out_out"] - decimal_precision
) * fix_csv_align
len_in_in = (db["inter_times"]["in_in"] - decimal_precision) * fix_csv_align
len_in_out = (
db["inter_times"]["in_out"] - decimal_precision
) * fix_csv_align
fmt = "{{:{}d}}".format(len_pid)
fmt += "{}{{:{}d}}".format(separator, len_tid)
fmt += "{}{{:>{}}}".format(separator, len_comm)
fmt += "{}{{:{}d}}".format(separator, len_runs)
fmt += "{}{{:{}.{}f}}".format(separator, len_acc, time_precision)
fmt += "{}{{}}{{:{}.{}f}}".format(separator, len_mean, time_precision)
fmt += "{}{{:{}.{}f}}".format(separator, len_median, time_precision)
fmt += "{}{{:{}.{}f}}".format(separator, len_min, time_precision)
fmt += "{}{{:{}.{}f}}".format(separator, len_max, time_precision)
fmt += "{}{{}}{{:{}.{}f}}{{}}{{}}".format(
separator, len_max_at, decimal_precision
)
if args.summary_extended:
fmt += "{}{{:{}.{}f}}".format(separator, len_out_in, time_precision)
fmt += "{}{{}}{{:{}.{}f}}{{}}".format(
separator, len_inter_at, decimal_precision
)
fmt += "{}{{:{}.{}f}}".format(separator, len_out_out, time_precision)
fmt += "{}{{:{}.{}f}}".format(separator, len_in_in, time_precision)
fmt += "{}{{:{}.{}f}}".format(separator, len_in_out, time_precision)
return fmt
def _calc_alignments_summary(self, align_helper):
# Length is being cut in 3 groups so that further addition is easier to handle.
# The length of every argument from the alignment helper is being checked if it
# is longer than the longest until now. In that case the length is being saved.
for key in db["task_info"]:
if len(str(getattr(align_helper, key))) > db["task_info"][key]:
db["task_info"][key] = len(str(getattr(align_helper, key)))
for key in db["runtime_info"]:
if len(str(getattr(align_helper, key))) > db["runtime_info"][key]:
db["runtime_info"][key] = len(str(getattr(align_helper, key)))
if args.summary_extended:
for key in db["inter_times"]:
if len(str(getattr(align_helper, key))) > db["inter_times"][key]:
db["inter_times"][key] = len(str(getattr(align_helper, key)))
def print(self):
self._task_stats()
fmt = self._format_stats()
if not args.csv_summary:
print("\nSummary")
self._print_header()
self._column_titles()
for i in range(len(self._body)):
fd_sum.write(fmt.format(*tuple(self._body[i])) + "\n")
class Task(object):
""" The class is used to handle the information of a given task."""
def __init__(self, id, tid, cpu, comm):
self.id = id
self.tid = tid
self.cpu = cpu
self.comm = comm
self.pid = None
self._time_in = None
self._time_out = None
def schedule_in_at(self, time):
"""set the time where the task was scheduled in"""
self._time_in = time
def schedule_out_at(self, time):
"""set the time where the task was scheduled out"""
self._time_out = time
def time_out(self, unit="s"):
"""return time where a given task was scheduled out"""
factor = time_uniter(unit)
return self._time_out * decimal.Decimal(factor)
def time_in(self, unit="s"):
"""return time where a given task was scheduled in"""
factor = time_uniter(unit)
return self._time_in * decimal.Decimal(factor)
def runtime(self, unit="us"):
factor = time_uniter(unit)
return (self._time_out - self._time_in) * decimal.Decimal(factor)
def update_pid(self, pid):
self.pid = pid
def _task_id(pid, cpu):
"""returns a "unique-enough" identifier, please do not change"""
return "{}-{}".format(pid, cpu)
def _filter_non_printable(unfiltered):
"""comm names may contain loony chars like '\x00000'"""
filtered = ""
for char in unfiltered:
if char not in string.printable:
continue
filtered += char
return filtered
def _fmt_header():
separator, fix_csv_align = _prepare_fmt_sep()
fmt = "{{:>{}}}".format(LEN_SWITCHED_IN*fix_csv_align)
fmt += "{}{{:>{}}}".format(separator, LEN_SWITCHED_OUT*fix_csv_align)
fmt += "{}{{:>{}}}".format(separator, LEN_CPU*fix_csv_align)
fmt += "{}{{:>{}}}".format(separator, LEN_PID*fix_csv_align)
fmt += "{}{{:>{}}}".format(separator, LEN_TID*fix_csv_align)
fmt += "{}{{:>{}}}".format(separator, LEN_COMM*fix_csv_align)
fmt += "{}{{:>{}}}".format(separator, LEN_RUNTIME*fix_csv_align)
fmt += "{}{{:>{}}}".format(separator, LEN_OUT_IN*fix_csv_align)
if args.extended_times:
fmt += "{}{{:>{}}}".format(separator, LEN_OUT_OUT*fix_csv_align)
fmt += "{}{{:>{}}}".format(separator, LEN_IN_IN*fix_csv_align)
fmt += "{}{{:>{}}}".format(separator, LEN_IN_OUT*fix_csv_align)
return fmt
def _fmt_body():
separator, fix_csv_align = _prepare_fmt_sep()
decimal_precision, time_precision = _prepare_fmt_precision()
fmt = "{{}}{{:{}.{}f}}".format(LEN_SWITCHED_IN*fix_csv_align, decimal_precision)
fmt += "{}{{:{}.{}f}}".format(
separator, LEN_SWITCHED_OUT*fix_csv_align, decimal_precision
)
fmt += "{}{{:{}d}}".format(separator, LEN_CPU*fix_csv_align)
fmt += "{}{{:{}d}}".format(separator, LEN_PID*fix_csv_align)
fmt += "{}{{}}{{:{}d}}{{}}".format(separator, LEN_TID*fix_csv_align)
fmt += "{}{{}}{{:>{}}}".format(separator, LEN_COMM*fix_csv_align)
fmt += "{}{{:{}.{}f}}".format(separator, LEN_RUNTIME*fix_csv_align, time_precision)
if args.extended_times:
fmt += "{}{{:{}.{}f}}".format(separator, LEN_OUT_IN*fix_csv_align, time_precision)
fmt += "{}{{:{}.{}f}}".format(separator, LEN_OUT_OUT*fix_csv_align, time_precision)
fmt += "{}{{:{}.{}f}}".format(separator, LEN_IN_IN*fix_csv_align, time_precision)
fmt += "{}{{:{}.{}f}}{{}}".format(
separator, LEN_IN_OUT*fix_csv_align, time_precision
)
else:
fmt += "{}{{:{}.{}f}}{{}}".format(
separator, LEN_OUT_IN*fix_csv_align, time_precision
)
return fmt
def _print_header():
fmt = _fmt_header()
header = ("Switched-In", "Switched-Out", "CPU", "PID", "TID", "Comm", "Runtime",
"Time Out-In")
if args.extended_times:
header += ("Time Out-Out", "Time In-In", "Time In-Out")
fd_task.write(fmt.format(*header) + "\n")
def _print_task_finish(task):
"""calculating every entry of a row and printing it immediately"""
c_row_set = ""
c_row_reset = ""
out_in = -1
out_out = -1
in_in = -1
in_out = -1
fmt = _fmt_body()
# depending on user provided highlight option we change the color
# for particular tasks
if str(task.tid) in args.highlight_tasks_map:
c_row_set = _COLORS[args.highlight_tasks_map[str(task.tid)]]
c_row_reset = _COLORS["reset"]
if task.comm in args.highlight_tasks_map:
c_row_set = _COLORS[args.highlight_tasks_map[task.comm]]
c_row_reset = _COLORS["reset"]
# grey-out entries if PID == TID, they
# are identical, no threaded model so the
# thread id (tid) do not matter
c_tid_set = ""
c_tid_reset = ""
if task.pid == task.tid:
c_tid_set = _COLORS["grey"]
c_tid_reset = _COLORS["reset"]
if task.tid in db["tid"]:
# get last task of tid
last_tid_task = db["tid"][task.tid][-1]
# feed the timespan calculate, last in tid db
# and second the current one
timespan_gap_tid = Timespans()
timespan_gap_tid.feed(last_tid_task)
timespan_gap_tid.feed(task)
out_in = timespan_gap_tid.out_in
out_out = timespan_gap_tid.out_out
in_in = timespan_gap_tid.in_in
in_out = timespan_gap_tid.in_out
if args.extended_times:
line_out = fmt.format(c_row_set, task.time_in(), task.time_out(), task.cpu,
task.pid, c_tid_set, task.tid, c_tid_reset, c_row_set, task.comm,
task.runtime(time_unit), out_in, out_out, in_in, in_out,
c_row_reset) + "\n"
else:
line_out = fmt.format(c_row_set, task.time_in(), task.time_out(), task.cpu,
task.pid, c_tid_set, task.tid, c_tid_reset, c_row_set, task.comm,
task.runtime(time_unit), out_in, c_row_reset) + "\n"
try:
fd_task.write(line_out)
except(IOError):
# don't mangle the output if user SIGINT this script
sys.exit()
def _record_cleanup(_list):
"""
no need to store more then one element if --summarize
is not enabled
"""
if not args.summary and len(_list) > 1:
_list = _list[len(_list) - 1 :]
def _record_by_tid(task):
tid = task.tid
if tid not in db["tid"]:
db["tid"][tid] = []
db["tid"][tid].append(task)
_record_cleanup(db["tid"][tid])
def _record_by_cpu(task):
cpu = task.cpu
if cpu not in db["cpu"]:
db["cpu"][cpu] = []
db["cpu"][cpu].append(task)
_record_cleanup(db["cpu"][cpu])
def _record_global(task):
"""record all executed task, ordered by finish chronological"""
db["global"].append(task)
_record_cleanup(db["global"])
def _handle_task_finish(tid, cpu, time, perf_sample_dict):
if tid == 0:
return
_id = _task_id(tid, cpu)
if _id not in db["running"]:
# may happen, if we missed the switch to
# event. Seen in combination with --exclude-perf
# where the start is filtered out, but not the
# switched in. Probably a bug in exclude-perf
# option.
return
task = db["running"][_id]
task.schedule_out_at(time)
# record tid, during schedule in the tid
# is not available, update now
pid = int(perf_sample_dict["sample"]["pid"])
task.update_pid(pid)
del db["running"][_id]
# print only tasks which are not being filtered and no print of trace
# for summary only, but record every task.
if not _limit_filtered(tid, pid, task.comm) and not args.summary_only:
_print_task_finish(task)
_record_by_tid(task)
_record_by_cpu(task)
_record_global(task)
def _handle_task_start(tid, cpu, comm, time):
if tid == 0:
return
if tid in args.tid_renames:
comm = args.tid_renames[tid]
_id = _task_id(tid, cpu)
if _id in db["running"]:
# handle corner cases where already running tasks
# are switched-to again - saw this via --exclude-perf
# recorded traces. We simple ignore this "second start"
# event.
return
assert _id not in db["running"]
task = Task(_id, tid, cpu, comm)
task.schedule_in_at(time)
db["running"][_id] = task
def _time_to_internal(time_ns):
"""
To prevent float rounding errors we use Decimal internally
"""
return decimal.Decimal(time_ns) / decimal.Decimal(1e9)
def _limit_filtered(tid, pid, comm):
if args.filter_tasks:
if str(tid) in args.filter_tasks or comm in args.filter_tasks:
return True
else:
return False
if args.limit_to_tasks:
if str(tid) in args.limit_to_tasks or comm in args.limit_to_tasks:
return False
else:
return True
def _argument_filter_sanity_check():
if args.limit_to_tasks and args.filter_tasks:
sys.exit("Error: Filter and Limit at the same time active.")
if args.extended_times and args.summary_only:
sys.exit("Error: Summary only and extended times active.")
if args.time_limit and ":" not in args.time_limit:
sys.exit(
"Error: No bound set for time limit. Please set bound by ':' e.g :123."
)
if args.time_limit and (args.summary or args.summary_only or args.summary_extended):
sys.exit("Error: Cannot set time limit and print summary")
if args.csv_summary:
args.summary = True
if args.csv == args.csv_summary:
sys.exit("Error: Chosen files for csv and csv summary are the same")
if args.csv and (args.summary_extended or args.summary) and not args.csv_summary:
sys.exit("Error: No file chosen to write summary to. Choose with --csv-summary "
"<file>")
if args.csv and args.summary_only:
sys.exit("Error: --csv chosen and --summary-only. Standard task would not be"
"written to csv file.")
def _argument_prepare_check():
global time_unit, fd_task, fd_sum
if args.filter_tasks:
args.filter_tasks = args.filter_tasks.split(",")
if args.limit_to_tasks:
args.limit_to_tasks = args.limit_to_tasks.split(",")
if args.time_limit:
args.time_limit = args.time_limit.split(":")
for rename_tuple in args.rename_comms_by_tids.split(","):
tid_name = rename_tuple.split(":")
if len(tid_name) != 2:
continue
args.tid_renames[int(tid_name[0])] = tid_name[1]
args.highlight_tasks_map = dict()
for highlight_tasks_tuple in args.highlight_tasks.split(","):
tasks_color_map = highlight_tasks_tuple.split(":")
# default highlight color to red if no color set by user
if len(tasks_color_map) == 1:
tasks_color_map.append("red")
if args.highlight_tasks and tasks_color_map[1].lower() not in _COLORS:
sys.exit(
"Error: Color not defined, please choose from grey,red,green,yellow,blue,"
"violet"
)
if len(tasks_color_map) != 2:
continue
args.highlight_tasks_map[tasks_color_map[0]] = tasks_color_map[1]
time_unit = "us"
if args.ns:
time_unit = "ns"
elif args.ms:
time_unit = "ms"
fd_task = sys.stdout
if args.csv:
args.stdio_color = "never"
fd_task = open(args.csv, "w")
print("generating csv at",args.csv,)
fd_sum = sys.stdout
if args.csv_summary:
args.stdio_color = "never"
fd_sum = open(args.csv_summary, "w")
print("generating csv summary at",args.csv_summary)
if not args.csv:
args.summary_only = True
def _is_within_timelimit(time):
"""
Check if a time limit was given by parameter, if so ignore the rest. If not,
process the recorded trace in its entirety.
"""
if not args.time_limit:
return True
lower_time_limit = args.time_limit[0]
upper_time_limit = args.time_limit[1]
# check for upper limit
if upper_time_limit == "":
if time >= decimal.Decimal(lower_time_limit):
return True
# check for lower limit
if lower_time_limit == "":
if time <= decimal.Decimal(upper_time_limit):
return True
# quit if time exceeds upper limit. Good for big datasets
else:
quit()
if lower_time_limit != "" and upper_time_limit != "":
if (time >= decimal.Decimal(lower_time_limit) and
time <= decimal.Decimal(upper_time_limit)):
return True
# quit if time exceeds upper limit. Good for big datasets
elif time > decimal.Decimal(upper_time_limit):
quit()
def _prepare_fmt_precision():
decimal_precision = 6
time_precision = 3
if args.ns:
decimal_precision = 9
time_precision = 0
return decimal_precision, time_precision
def _prepare_fmt_sep():
separator = " "
fix_csv_align = 1
if args.csv or args.csv_summary:
separator = ";"
fix_csv_align = 0
return separator, fix_csv_align
def trace_unhandled(event_name, context, event_fields_dict, perf_sample_dict):
pass
def trace_begin():
_parse_args()
_check_color()
_init_db()
if not args.summary_only:
_print_header()
def trace_end():
if args.summary or args.summary_extended or args.summary_only:
Summary().print()
def sched__sched_switch(event_name, context, common_cpu, common_secs, common_nsecs,
common_pid, common_comm, common_callchain, prev_comm,
prev_pid, prev_prio, prev_state, next_comm, next_pid,
next_prio, perf_sample_dict):
# ignore common_secs & common_nsecs cause we need
# high res timestamp anyway, using the raw value is
# faster
time = _time_to_internal(perf_sample_dict["sample"]["time"])
if not _is_within_timelimit(time):
# user specific --time-limit a:b set
return
next_comm = _filter_non_printable(next_comm)
_handle_task_finish(prev_pid, common_cpu, time, perf_sample_dict)
_handle_task_start(next_pid, common_cpu, next_comm, time)