From: Julien Desfossez Date: Tue, 21 Feb 2012 21:59:26 +0000 (-0500) Subject: Initial import of code. X-Git-Tag: v0.2~71 X-Git-Url: https://git.lttng.org/?p=lttngtop.git;a=commitdiff_plain;h=1fc22eb45fde328b82aa5a5e296fdc086e77a32e Initial import of code. This is the first code base which uses the babeltrace API instead of being built inside the babeltrace tree. Signed-off-by: Julien Desfossez --- 1fc22eb45fde328b82aa5a5e296fdc086e77a32e diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4f465a4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.o +*.a +*.la +*.lo +.libs +.deps +*.bkp +/config.h +/config.h.in +/config.status +*.log +*.m4 +*.patch +libtool +/configure +Makefile +Makefile.in +autom4te.cache/ +config/ +core +stamp-h1 diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..013e59d --- /dev/null +++ b/ChangeLog @@ -0,0 +1,2 @@ +2012-02-16 LTTngTop 0.2 + * Remove internal Babeltrace code, use the API diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f1f4a4c --- /dev/null +++ b/LICENSE @@ -0,0 +1,8 @@ +LTTngTop +Julien Desfossez +February 16, 2012 + +* GPLv2 + +All source code is distributed under the GPLv2 license, as specified in the +per-file license. See gpl-2.0.txt for details. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..009425c --- /dev/null +++ b/Makefile.am @@ -0,0 +1,5 @@ +AM_CFLAGS = $(PACKAGE_CFLAGS) + +ACLOCAL_AMFLAGS = -I m4 + +SUBDIRS = lttngtop diff --git a/README b/README new file mode 100644 index 0000000..592e109 --- /dev/null +++ b/README @@ -0,0 +1,93 @@ +lttngtop +Julien Desfossez +August 2011 + +Lttngtop is an ncurses interface for reading and browsing traces recorded by +the LTTng tracer and displaying various statistics. +As of now, the cpu usage and perf counters are displayed. This version currently +only supports offline traces, but a live version is in alpha and will be +available for testing soon. + +USAGE +----- + +Record a trace with LTTng 2.0 with at least the sched_switch event, and the pid, +procname, tid, ppid contexts. To have the perftop view working you can enable +any number of perf contexts (depending on your hardware). +For the iotop view, you need to have some syscall events : sys_read, sys_write +and exit_syscall. + +Once your trace is recorded, you can use lttngtop this way : +$ lttngtop /path/to/your/trace + +Make sure you have read permissions on the whole directory. + +NOTE +---- + +Since this is an early release of lttngtop, it is still inside the babeltrace +source tree. As soon as all babeltrace dependencies are available from the +library and header files, lttngtop will be in its own repository. Until then, +changes in the babeltrace source tree will be merged in this tree to avoid +conflicts. This repository doesn't install the babeltrace binary, or any of its +libraries. This way you can safely install it in the default locations. + +EXAMPLE +------- + +# lttng create lttngtop +# lttng enable-event -k sched_switch +# lttng add-context -k -t pid -t procname -t tid -t ppid -t perf:cache-misses \ + -t perf:major-faults -t perf:branch-load-misses +# lttng start +# ...do stuff... +# lttng stop +# lttng destroy + +$ lttngtop $HOME/lttng-traces/lttngtop-... + +BUILDING +-------- + + ./bootstrap (skip if using tarball) + ./configure + make + make install + +DEPENDENCIES +------------ + +To compile Babeltrace and lttngtop, you will need: + + gcc 3.2 or better + libc6 development librairies + (Debian : libc6, libc6-dev) + (Fedora : glibc, glibc) + glib 2.22 or better development libraries + (Debian : libglib2.0-0, libglib2.0-dev) + (Fedora : glib2, glib2-devel) + uuid development libraries + (Debian : uuid-dev) + (Fedora : uuid-devel) + libpopt >= 1.13 development libraries + (Debian : libpopt-dev) + (Fedora : popt) + ncurses development libraries + (Debian : libncurses5-dev) + +For developers using the git tree: + +This source tree is based on the autotools suite from GNU to simplify +portability. Here are some things you should have on your system in order to +compile the git repository tree : + +- GNU autotools (automake >=1.10, autoconf >=2.50, autoheader >=2.50) + (make sure your system wide "automake" points to a recent version!) +- GNU Libtool >=2.2 + (for more information, go to http://www.gnu.org/software/autoconf/) +- Flex >=2.5.35. +- Bison >=2.4. + +If you get the tree from the repository, you will need to use the "bootstrap" +script in the root of the tree. It calls all the GNU tools needed to prepare the +tree configuration. diff --git a/bootstrap b/bootstrap new file mode 100755 index 0000000..c507425 --- /dev/null +++ b/bootstrap @@ -0,0 +1,12 @@ +#! /bin/sh + +set -x +if [ ! -e config ]; then + mkdir config +fi +aclocal +libtoolize --force --copy +autoheader +automake --add-missing --copy +autoconf + diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..d38cefb --- /dev/null +++ b/configure.ac @@ -0,0 +1,71 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_INIT([lttngtop],[0.2],[julien dot desfossez at efficios dot com]) +AC_CONFIG_AUX_DIR([config]) +AC_CANONICAL_TARGET +AC_CANONICAL_HOST +AM_INIT_AUTOMAKE([foreign dist-bzip2 no-dist-gzip]) +AM_SILENT_RULES([yes]) + +AC_CONFIG_MACRO_DIR([m4]) + +AC_CONFIG_HEADERS([config.h]) + +# Checks for programs. +AC_PROG_CC +AC_PROG_MAKE_SET +LT_INIT +AC_PROG_YACC +AC_PROG_LEX + +# Checks for typedefs, structures, and compiler characteristics. +AC_C_INLINE +AC_TYPE_PID_T +AC_TYPE_SIZE_T + +# Checks for library functions. +AC_FUNC_MALLOC +AC_FUNC_MMAP +AC_CHECK_FUNCS([bzero gettimeofday munmap strtoul]) + +# Check for libuuid +AC_CHECK_LIB([uuid], [uuid_generate], [], + [AC_MSG_ERROR([Cannot find the libuuid library.])] +) + +# Check for libpopt +AC_CHECK_LIB([popt], [poptGetContext], [], + [AC_MSG_ERROR([Cannot find the popt library.])] +) + +# Check for libncurses +AC_CHECK_LIB([ncurses], [initscr], [], + [AC_MSG_ERROR([Cannot find the the ncurses library.])] +) + +# Check for libpanel, also part of libncurses +# (but this check will add the required -lpanel flag) +AC_CHECK_LIB([panel], [update_panels], [], + [AC_MSG_ERROR([Cannot find the the ncurses library.])] +) + +# Check for Glib. It needs to be installed anyway or this macro will not be defined. +AM_PATH_GLIB_2_0([2.22.0], [], + [AC_MSG_ERROR([Glib 2.22 is required in order to compile LTTngTop. +Please install the Glib development files.])], [gmodule] +) + +pkg_modules="gmodule-2.0 >= 2.0.0" +PKG_CHECK_MODULES(GMODULE, [$pkg_modules]) +AC_SUBST(PACKAGE_LIBS) +LIBS="$LIBS $GMODULE_LIBS" + +PACKAGE_CFLAGS="$GMODULE_CFLAGS -Wall -Werror=format-security" +AC_SUBST(PACKAGE_CFLAGS) + +AC_CONFIG_FILES([ + Makefile + lttngtop/Makefile +]) +AC_OUTPUT diff --git a/gpl-2.0.txt b/gpl-2.0.txt new file mode 100644 index 0000000..d511905 --- /dev/null +++ b/gpl-2.0.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/lttngtop/Makefile.am b/lttngtop/Makefile.am new file mode 100644 index 0000000..5d6ca3e --- /dev/null +++ b/lttngtop/Makefile.am @@ -0,0 +1,12 @@ +AM_CFLAGS = $(PACKAGE_CFLAGS) + +bin_PROGRAMS = lttngtop + +lttngtop_SOURCES = \ + lttngtop.c \ + common.c \ + cursesdisplay.c \ + cputop.c \ + iostreamtop.c + +lttngtop_LDADD = -lbabeltrace -lctf diff --git a/lttngtop/common.c b/lttngtop/common.c new file mode 100644 index 0000000..2a0f1f4 --- /dev/null +++ b/lttngtop/common.c @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2011 Julien Desfossez + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation; + * + * 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 +#include +#include "common.h" + +struct processtop *find_process_tid(struct lttngtop *ctx, int tid, char *comm) +{ + gint i; + struct processtop *tmp; + + for (i = 0; i < ctx->process_table->len; i++) { + tmp = g_ptr_array_index(ctx->process_table, i); + if (tmp && tmp->tid == tid) + return tmp; + } + return NULL; +} + +struct processtop* add_proc(struct lttngtop *ctx, int tid, char *comm, + unsigned long timestamp) +{ + struct processtop *newproc; + + /* if the PID already exists, we just rename the process */ + /* FIXME : need to integrate with clone/fork/exit to be accurate */ + newproc = find_process_tid(ctx, tid, comm); + if (!newproc) { + newproc = malloc(sizeof(struct processtop)); + memset(newproc, 0, sizeof(struct processtop)); + newproc->tid = tid; + newproc->birth = timestamp; + newproc->process_files_table = g_ptr_array_new(); + newproc->threads = g_ptr_array_new(); + newproc->perf = g_hash_table_new(g_direct_hash, g_direct_equal); + newproc->iostream = malloc(sizeof(struct iostream)); + newproc->iostream->ret_read = 0; + newproc->iostream->ret_write = 0; + newproc->iostream->ret_total = 0; + newproc->iostream->syscall_info = NULL; + g_ptr_array_add(ctx->process_table, newproc); + } + newproc->comm = strdup(comm); + + return newproc; +} + +struct processtop* update_proc(struct processtop* proc, int pid, int tid, + int ppid, char *comm) +{ + if (proc) { + proc->pid = pid; + proc->tid = tid; + proc->ppid = ppid; + if (strcmp(proc->comm, comm) != 0) { + free(proc->comm); + proc->comm = strdup(comm); + } + } + return proc; +} + +/* + * This function just sets the time of death of a process. + * When we rotate the cputime we remove it from the process list. + */ +void death_proc(struct lttngtop *ctx, int tid, char *comm, + unsigned long timestamp) +{ + struct processtop *tmp; + tmp = find_process_tid(ctx, tid, comm); + if (tmp && strcmp(tmp->comm, comm) == 0) + tmp->death = timestamp; +} + +struct processtop* get_proc(struct lttngtop *ctx, int tid, char *comm, + unsigned long timestamp) +{ + struct processtop *tmp; + tmp = find_process_tid(ctx, tid, comm); + if (tmp && strcmp(tmp->comm, comm) == 0) + return tmp; + return add_proc(ctx, tid, comm, timestamp); +} + +void add_thread(struct processtop *parent, struct processtop *thread) +{ + gint i; + struct processtop *tmp; + + for (i = 0; i < parent->threads->len; i++) { + tmp = g_ptr_array_index(parent->threads, i); + if (tmp == thread) + return; + } + g_ptr_array_add(parent->threads, thread); +} + +struct cputime* add_cpu(int cpu) +{ + struct cputime *newcpu; + + newcpu = malloc(sizeof(struct cputime)); + newcpu->id = cpu; + newcpu->current_task = NULL; + newcpu->perf = g_hash_table_new(g_direct_hash, g_direct_equal); + + g_ptr_array_add(lttngtop.cpu_table, newcpu); + + return newcpu; +} +struct cputime* get_cpu(int cpu) +{ + gint i; + struct cputime *tmp; + + for (i = 0; i < lttngtop.cpu_table->len; i++) { + tmp = g_ptr_array_index(lttngtop.cpu_table, i); + if (tmp->id == cpu) + return tmp; + } + + return add_cpu(cpu); +} + +/* + * At the end of a sampling period, we need to display the cpu time for each + * process and to reset it to zero for the next period + */ +void rotate_cputime(unsigned long end) +{ + gint i; + struct cputime *tmp; + unsigned long elapsed; + + for (i = 0; i < lttngtop.cpu_table->len; i++) { + tmp = g_ptr_array_index(lttngtop.cpu_table, i); + elapsed = end - tmp->task_start; + if (tmp->current_task) { + tmp->current_task->totalcpunsec += elapsed; + tmp->current_task->threadstotalcpunsec += elapsed; + if (tmp->current_task->pid != tmp->current_task->tid && + tmp->current_task->threadparent) { + tmp->current_task->threadparent->threadstotalcpunsec += elapsed; + } + } + tmp->task_start = end; + } +} + +void reset_perf_counter(gpointer key, gpointer value, gpointer user_data) +{ + ((struct perfcounter*) value)->count = 0; +} + +void copy_perf_counter(gpointer key, gpointer value, gpointer new_table) +{ + struct perfcounter *newperf; + + newperf = malloc(sizeof(struct perfcounter)); + newperf->count = ((struct perfcounter *) value)->count; + newperf->visible = ((struct perfcounter *) value)->visible; + newperf->sort = ((struct perfcounter *) value)->sort; + g_hash_table_insert((GHashTable *) new_table, key, newperf); +} + +void rotate_perfcounter() { + int i; + struct processtop *tmp; + for (i = 0; i < lttngtop.process_table->len; i++) { + tmp = g_ptr_array_index(lttngtop.process_table, i); + g_hash_table_foreach(tmp->perf, reset_perf_counter, NULL); + } +} + +void cleanup_processtop() +{ + gint i; + struct processtop *tmp; + + for (i = 0; i < lttngtop.process_table->len; i++) { + tmp = g_ptr_array_index(lttngtop.process_table, i); + tmp->totalcpunsec = 0; + tmp->threadstotalcpunsec = 0; + tmp->iostream->ret_read = 0; + tmp->iostream->ret_write = 0; + } +} + +struct lttngtop* get_copy_lttngtop(unsigned long start, unsigned long end) +{ + gint i, j; + unsigned long time; + struct lttngtop *dst; + struct processtop *tmp, *tmp2, *new; + struct cputime *tmpcpu, *newcpu; + struct files *tmpfile, *newfile; + + dst = malloc(sizeof(struct lttngtop)); + dst = memset(dst, 0, sizeof(struct lttngtop)); + dst->start = start; + dst->end = end; + dst->process_table = g_ptr_array_new(); + dst->files_table = g_ptr_array_new(); + dst->cpu_table = g_ptr_array_new(); + dst->perf_list = g_hash_table_new(g_direct_hash, g_direct_equal); + + rotate_cputime(end); + + g_hash_table_foreach(lttngtop.perf_list, copy_perf_counter, dst->perf_list); + for (i = 0; i < lttngtop.process_table->len; i++) { + tmp = g_ptr_array_index(lttngtop.process_table, i); + new = malloc(sizeof(struct processtop)); + + memcpy(new, tmp, sizeof(struct processtop)); + new->threads = g_ptr_array_new(); + new->comm = strdup(tmp->comm); + new->process_files_table = g_ptr_array_new(); + new->perf = g_hash_table_new(g_direct_hash, g_direct_equal); + g_hash_table_foreach(tmp->perf, copy_perf_counter, new->perf); + + new->iostream = malloc(sizeof(struct iostream)); + memcpy(new->iostream, tmp->iostream, sizeof(struct iostream)); + /* compute the stream speed */ + if (end - start != 0) + { + time = (end - start)/NSEC_PER_SEC; + new->iostream->ret_read = new->iostream->ret_read/(time); + new->iostream->ret_write = new->iostream->ret_write/(time); + } + + for (j = 0; j < tmp->process_files_table->len; j++) { + tmpfile = g_ptr_array_index(tmp->process_files_table, j); + newfile = malloc(sizeof(struct files)); + + memcpy(newfile, tmpfile, sizeof(struct files)); + + newfile->name = strdup(tmpfile->name); + newfile->ref = new; + + g_ptr_array_add(new->process_files_table, newfile); + g_ptr_array_add(dst->files_table, newfile); + + /* + * if the process died during the last period, we remove all + * files associated with if after the copy + */ + if (tmp->death > 0 && tmp->death < end) { + g_ptr_array_remove(tmp->process_files_table, tmpfile); + free(tmpfile); + } + } + g_ptr_array_add(dst->process_table, new); + + /* + * if the process died during the last period, we remove it from + * the current process list after the copy + */ + if (tmp->death > 0 && tmp->death < end) { + g_ptr_array_remove(lttngtop.process_table, tmp); + g_ptr_array_free(tmp->threads, TRUE); + free(tmp->comm); + g_ptr_array_free(tmp->process_files_table, TRUE); + g_hash_table_destroy(tmp->perf); + free(tmp); + } + } + rotate_perfcounter(); + + for (i = 0; i < lttngtop.cpu_table->len; i++) { + tmpcpu = g_ptr_array_index(lttngtop.cpu_table, i); + newcpu = malloc(sizeof(struct cputime)); + memcpy(newcpu, tmpcpu, sizeof(struct cputime)); + newcpu->perf = g_hash_table_new(g_direct_hash, g_direct_equal); + g_hash_table_foreach(tmpcpu->perf, copy_perf_counter, newcpu->perf); + /* + * note : we don't care about the current process pointer in the copy + * so the reference is invalid after the memcpy + */ + g_ptr_array_add(dst->cpu_table, newcpu); + } + /* create the threads index if required */ + for (i = 0; i < dst->process_table->len; i++) { + tmp = g_ptr_array_index(dst->process_table, i); + if (tmp->pid == tmp->tid) { + for (j = 0; j < dst->process_table->len; j++) { + tmp2 = g_ptr_array_index(dst->process_table, j); + if (tmp2->pid == tmp->pid) { + tmp2->threadparent = tmp; + g_ptr_array_add(tmp->threads, tmp2); + } + } + } + } + + // update_global_stats(dst); + cleanup_processtop(); + + return dst; +} + diff --git a/lttngtop/common.h b/lttngtop/common.h new file mode 100644 index 0000000..e81256c --- /dev/null +++ b/lttngtop/common.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2011 Julien Desfossez + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation; + * + * 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. + */ + +#ifndef _COMMON_H +#define _COMMON_H + +#include +#include +#include "lttngtoptypes.h" +#include "cputop.h" + +#define NSEC_PER_USEC 1000 +#define NSEC_PER_SEC 1000000000L + +sem_t goodtodisplay, goodtoupdate, timer, pause_sem, end_trace_sem, bootstrap; + +GPtrArray *copies; /* struct lttngtop */ +pthread_mutex_t perf_list_mutex; + +struct lttngtop *data; + +struct processtop *find_process_tid(struct lttngtop *ctx, int pid, char *comm); +struct processtop* add_proc(struct lttngtop *ctx, int pid, char *comm, + unsigned long timestamp); +struct processtop* update_proc(struct processtop* proc, int pid, int tid, + int ppid, char *comm); +void add_thread(struct processtop *parent, struct processtop *thread); +struct processtop* get_proc(struct lttngtop *ctx, int tid, char *comm, + unsigned long timestamp); +void death_proc(struct lttngtop *ctx, int tid, char *comm, + unsigned long timestamp); +struct cputime* add_cpu(int cpu); +struct cputime* get_cpu(int cpu); +struct lttngtop* get_copy_lttngtop(unsigned long start, unsigned long end); +struct perfcounter *add_perf_counter(GPtrArray *perf, GQuark quark, + unsigned long count); +struct perfcounter *get_perf_counter(const char *name, struct processtop *proc, + struct cputime *cpu); + +#endif /* _COMMON_H */ diff --git a/lttngtop/cputop.c b/lttngtop/cputop.c new file mode 100644 index 0000000..2ade0db --- /dev/null +++ b/lttngtop/cputop.c @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2011 Julien Desfossez + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation; + * + * 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 + +#include "lttngtoptypes.h" +#include "common.h" +#include "cputop.h" + +void update_cputop_data(unsigned long timestamp, int64_t cpu, int prev_pid, + int next_pid, char *prev_comm, char *next_comm) +{ + struct cputime *tmpcpu; + unsigned long elapsed; + + tmpcpu = get_cpu(cpu); + + if (tmpcpu->current_task && tmpcpu->current_task->pid == prev_pid) { + elapsed = timestamp - tmpcpu->task_start; + tmpcpu->current_task->totalcpunsec += elapsed; + tmpcpu->current_task->threadstotalcpunsec += elapsed; + if (tmpcpu->current_task->pid != tmpcpu->current_task->tid) + tmpcpu->current_task->threadparent->threadstotalcpunsec += elapsed; + } + + if (next_pid != 0) + tmpcpu->current_task = get_proc(<tngtop, next_pid, next_comm, timestamp); + else + tmpcpu->current_task = NULL; + + tmpcpu->task_start = timestamp; +} + +enum bt_cb_ret handle_sched_switch(struct bt_ctf_event *call_data, + void *private_data) +{ + struct definition *scope; + unsigned long timestamp; + uint64_t cpu_id; + char *prev_comm, *next_comm; + int prev_tid, next_tid; + + timestamp = bt_ctf_get_timestamp(call_data); + if (timestamp == -1ULL) + goto error; + + scope = bt_ctf_get_top_level_scope(call_data, + BT_EVENT_FIELDS); + prev_comm = bt_ctf_get_char_array(bt_ctf_get_field(call_data, + scope, "_prev_comm")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing prev_comm context info\n"); + goto error; + } + + next_comm = bt_ctf_get_char_array(bt_ctf_get_field(call_data, + scope, "_next_comm")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing next_comm context info\n"); + goto error; + } + + prev_tid = bt_ctf_get_int64(bt_ctf_get_field(call_data, + scope, "_prev_tid")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing prev_tid context info\n"); + goto error; + } + + next_tid = bt_ctf_get_int64(bt_ctf_get_field(call_data, + scope, "_next_tid")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing next_tid context info\n"); + goto error; + } + + scope = bt_ctf_get_top_level_scope(call_data, + BT_STREAM_PACKET_CONTEXT); + cpu_id = bt_ctf_get_uint64(bt_ctf_get_field(call_data, + scope, "cpu_id")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing cpu_id context info\n"); + goto error; + } + + update_cputop_data(timestamp, cpu_id, prev_tid, next_tid, + prev_comm, next_comm); + + return BT_CB_OK; + +error: + return BT_CB_ERROR_STOP; +} + +enum bt_cb_ret handle_sched_process_free(struct bt_ctf_event *call_data, + void *private_data) +{ + struct definition *scope; + unsigned long timestamp; + char *comm; + int tid; + + timestamp = bt_ctf_get_timestamp(call_data); + if (timestamp == -1ULL) + goto error; + + scope = bt_ctf_get_top_level_scope(call_data, + BT_EVENT_FIELDS); + comm = bt_ctf_get_char_array(bt_ctf_get_field(call_data, + scope, "_comm")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing procname context info\n"); + goto error; + } + + tid = bt_ctf_get_int64(bt_ctf_get_field(call_data, + scope, "_tid")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing tid context info\n"); + goto error; + } + + death_proc(<tngtop, tid, comm, timestamp); + + return BT_CB_OK; + +error: + return BT_CB_ERROR_STOP; + +} + diff --git a/lttngtop/cputop.h b/lttngtop/cputop.h new file mode 100644 index 0000000..8b57823 --- /dev/null +++ b/lttngtop/cputop.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2011 Julien Desfossez + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation; + * + * 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. + */ + +#ifndef _LTTNGTOP_H +#define _LTTNGTOP_H + +#include +#include +#include +#include + +enum bt_cb_ret handle_sched_switch(struct bt_ctf_event *hook_data, + void *call_data); + +enum bt_cb_ret handle_sched_process_free(struct bt_ctf_event *call_data, + void *private_data); + +#endif /* _LTTNGTOP_H */ diff --git a/lttngtop/cursesdisplay.c b/lttngtop/cursesdisplay.c new file mode 100644 index 0000000..0f79485 --- /dev/null +++ b/lttngtop/cursesdisplay.c @@ -0,0 +1,1003 @@ +/* + * Copyright (C) 2011 Julien Desfossez + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation; + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include "cursesdisplay.h" +#include "lttngtoptypes.h" +#include "common.h" + +#define DEFAULT_DELAY 15 +#define MAX_LINE_LENGTH 50 +#define MAX_LOG_LINES 4 + +/* to prevent concurrent updates of the different windows */ +sem_t update_display_sem; + +char *termtype; +WINDOW *footer, *header, *center, *status; +WINDOW *perf_panel_window = NULL; +PANEL *perf_panel, *main_panel; + +int perf_panel_visible = 0; +int perf_line_selected = 0; + +int last_display_index, currently_displayed_index; + +struct processtop *selected_process = NULL; +int selected_tid; +char *selected_comm; +int selected_ret; + +int selected_line = 0; /* select bar position */ +int selected_in_list = 0; /* selection relative to the whole list */ +int list_offset = 0; /* first index in the list to display (scroll) */ +int nb_log_lines = 0; +char log_lines[MAX_LINE_LENGTH * MAX_LOG_LINES + MAX_LOG_LINES]; + +int max_elements = 80; + +int toggle_threads = -1; +int toggle_pause = -1; +int toggle_tree = -1; + +int max_center_lines; + +pthread_t keyboard_thread; + +void reset_ncurses() +{ + curs_set(1); + endwin(); + exit(0); +} + +static void handle_sigterm(int signal) +{ + reset_ncurses(); +} + +void init_screen() +{ + initscr(); + noecho(); + halfdelay(DEFAULT_DELAY); + nonl(); + intrflush(stdscr, false); + keypad(stdscr, true); + curs_set(0); + + if (has_colors()) { + start_color(); + init_pair(1, COLOR_RED, COLOR_BLACK); /* - */ + init_pair(2, COLOR_GREEN, COLOR_BLACK); /* + */ + init_pair(3, COLOR_BLACK, COLOR_WHITE); /* keys */ + init_pair(4, COLOR_WHITE, COLOR_GREEN); /* keys activated */ + init_pair(5, COLOR_WHITE, COLOR_BLUE); /* select line */ + } + termtype = getenv("TERM"); + if (!strcmp(termtype, "xterm") || !strcmp(termtype, "xterm-color") || + !strcmp(termtype, "vt220")) { + define_key("\033[H", KEY_HOME); + define_key("\033[F", KEY_END); + define_key("\033OP", KEY_F(1)); + define_key("\033OQ", KEY_F(2)); + define_key("\033OR", KEY_F(3)); + define_key("\033OS", KEY_F(4)); + define_key("\0330U", KEY_F(6)); + define_key("\033[11~", KEY_F(1)); + define_key("\033[12~", KEY_F(2)); + define_key("\033[13~", KEY_F(3)); + define_key("\033[14~", KEY_F(4)); + define_key("\033[16~", KEY_F(6)); + define_key("\033[17;2~", KEY_F(18)); + } + signal(SIGTERM, handle_sigterm); + mousemask(BUTTON1_CLICKED, NULL); + refresh(); +} + +WINDOW *create_window(int height, int width, int startx, int starty) +{ + WINDOW *win; + win = newwin(height, width, startx, starty); + box(win, 0 , 0); + wrefresh(win); + return win; +} + +WINDOW *create_window_no_border(int height, int width, int startx, int starty) +{ + WINDOW *win; + win = newwin(height, width, startx, starty); + wrefresh(win); + return win; +} + +void print_digit(WINDOW *win, int digit) +{ + if (digit < 0) { + wattron(win, COLOR_PAIR(1)); + wprintw(win, "%d", digit); + wattroff(win, COLOR_PAIR(1)); + } else if (digit > 0) { + wattron(win, COLOR_PAIR(2)); + wprintw(win, "+%d", digit); + wattroff(win, COLOR_PAIR(2)); + } else { + wprintw(win, "0"); + } +} + +void print_digits(WINDOW *win, int first, int second) +{ + wprintw(win, "("); + print_digit(win, first); + wprintw(win, ", "); + print_digit(win, second); + wprintw(win, ")"); +} + +void print_headers(int line, char *desc, int value, int first, int second) +{ + wattron(header, A_BOLD); + mvwprintw(header, line, 4, "%s", desc); + wattroff(header, A_BOLD); + mvwprintw(header, line, 16, "N/A", value); + wmove(header, line, 24); + print_digits(header, first, second); + wmove(header, line, 40); +} + +void set_window_title(WINDOW *win, char *title) +{ + wattron(win, A_BOLD); + mvwprintw(win, 0, 1, title); + wattroff(win, A_BOLD); +} + +void print_log(char *str) +{ + int i; + int current_line = 1; + int current_char = 1; + char *tmp, *tmp2; + /* rotate the line buffer */ + if (nb_log_lines >= MAX_LOG_LINES) { + tmp = strndup(log_lines, MAX_LINE_LENGTH * MAX_LOG_LINES + MAX_LOG_LINES); + tmp2 = strchr(tmp, '\n'); + memset(log_lines, '\0', strlen(log_lines)); + strncat(log_lines, tmp2 + 1, strlen(tmp2) - 1); + log_lines[strlen(log_lines)] = '\n'; + log_lines[strlen(log_lines)] = '\0'; + free(tmp); + } + nb_log_lines++; + + strncat(log_lines, str, MAX_LINE_LENGTH - 1); + + if (nb_log_lines < MAX_LOG_LINES) + log_lines[strlen(log_lines)] = '\n'; + log_lines[strlen(log_lines)] = '\0'; + + werase(status); + box(status, 0 , 0); + set_window_title(status, "Status"); + for (i = 0; i < strlen(log_lines); i++) { + if (log_lines[i] == '\n') { + wmove(status, ++current_line, 1); + current_char = 1; + } else { + mvwprintw(status, current_line, current_char++, "%c", log_lines[i]); + } + } + wrefresh(status); +} + +void print_key(WINDOW *win, char *key, char *desc, int toggle) +{ + int pair; + if (toggle > 0) + pair = 4; + else + pair = 3; + wattron(win, COLOR_PAIR(pair)); + wprintw(footer, "%s", key); + wattroff(win, COLOR_PAIR(pair)); + wprintw(footer, ":%s", desc); +} + +void update_footer() +{ + sem_wait(&update_display_sem); + werase(footer); + wmove(footer, 1, 1); + print_key(footer, "F2", "CPUtop ", current_view == cpu); + print_key(footer, "F3", "PerfTop ", current_view == perf); + print_key(footer, "F6", "IOTop ", current_view == iostream); + print_key(footer, "Enter", "Details ", current_view == process_details); + print_key(footer, "q", "Quit | ", 0); + print_key(footer, "P", "Perf Pref ", 0); + print_key(footer, "p", "Pause ", toggle_pause); + + wrefresh(footer); + sem_post(&update_display_sem); +} + +void basic_header() +{ + werase(header); + box(header, 0 , 0); + set_window_title(header, "Statistics for interval [gathering data...["); + wattron(header, A_BOLD); + mvwprintw(header, 1, 4, "CPUs"); + mvwprintw(header, 2, 4, "Processes"); + mvwprintw(header, 3, 4, "Threads"); + mvwprintw(header, 4, 4, "Files"); + mvwprintw(header, 5, 4, "Network"); + mvwprintw(header, 6, 4, "IO"); + wattroff(header, A_BOLD); + wrefresh(header); +} + +void update_header() +{ + werase(header); + box(header, 0 , 0); + set_window_title(header, "Statistics for interval "); + wattron(header, A_BOLD); + /* + wprintw(header, "[%lu.%lu, %lu.%lu[", + data->start.tv_sec, data->start.tv_nsec, + data->end.tv_sec, data->end.tv_nsec); + */ + wprintw(header, "[%lu, %lu[", + data->start, + data->end); + mvwprintw(header, 1, 4, "CPUs"); + wattroff(header, A_BOLD); + wprintw(header, "\t%d\t(max/cpu : %0.2f%)", data->cpu_table->len, + 100.0/data->cpu_table->len); + print_headers(2, "Processes", data->nbproc, data->nbnewproc, + -1*(data->nbdeadproc)); + print_headers(3, "Threads", data->nbthreads, data->nbnewthreads, + -1*(data->nbdeadthreads)); + print_headers(4, "Files", data->nbfiles, data->nbnewfiles, + -1*(data->nbclosedfiles)); + mvwprintw(header, 4, 43, "N/A kbytes/sec"); + print_headers(5, "Network", 114, 0, 0); + mvwprintw(header, 5, 43, "N/A Mbytes/sec"); + wrefresh(header); +} + +gint sort_by_cpu_desc(gconstpointer p1, gconstpointer p2) +{ + struct processtop *n1 = *(struct processtop **)p1; + struct processtop *n2 = *(struct processtop **)p2; + unsigned long totaln1 = n1->totalcpunsec; + unsigned long totaln2 = n2->totalcpunsec; + + if (totaln1 < totaln2) + return 1; + if (totaln1 == totaln2) + return 0; + return -1; +} + +gint sort_by_cpu_group_by_threads_desc(gconstpointer p1, gconstpointer p2) +{ + struct processtop *n1 = *(struct processtop **)p1; + struct processtop *n2 = *(struct processtop **)p2; + unsigned long totaln1 = n1->threadstotalcpunsec; + unsigned long totaln2 = n2->threadstotalcpunsec; + + if (totaln1 < totaln2) + return 1; + if (totaln1 == totaln2) + return 0; + return -1; +} + +void update_cputop_display() +{ + int i; + int header_offset = 2; + struct processtop *tmp; + unsigned long elapsed; + double maxcputime; + int nblinedisplayed = 0; + int current_line = 0; + + elapsed = data->end - data->start; + maxcputime = elapsed * data->cpu_table->len / 100.0; + + g_ptr_array_sort(data->process_table, sort_by_cpu_desc); + + set_window_title(center, "CPU Top"); + wattron(center, A_BOLD); + mvwprintw(center, 1, 1, "CPU(%)"); + mvwprintw(center, 1, 12, "TGID"); + mvwprintw(center, 1, 22, "PID"); + mvwprintw(center, 1, 32, "NAME"); + wattroff(center, A_BOLD); + + max_center_lines = LINES - 7 - 7 - 1 - header_offset; + + /* iterate the process (thread) list */ + for (i = list_offset; i < data->process_table->len && + nblinedisplayed < max_center_lines; i++) { + tmp = g_ptr_array_index(data->process_table, i); + + if (current_line == selected_line) { + selected_process = tmp; + selected_tid = tmp->tid; + selected_comm = tmp->comm; + wattron(center, COLOR_PAIR(5)); + mvwhline(center, current_line + header_offset, 1, ' ', COLS-3); + } + /* CPU(%) */ + mvwprintw(center, current_line + header_offset, 1, "%1.2f", + tmp->totalcpunsec / maxcputime); + /* TGID */ + mvwprintw(center, current_line + header_offset, 12, "%d", tmp->pid); + /* PID */ + mvwprintw(center, current_line + header_offset, 22, "%d", tmp->tid); + /* NAME */ + mvwprintw(center, current_line + header_offset, 32, "%s", tmp->comm); + wattroff(center, COLOR_PAIR(5)); + nblinedisplayed++; + current_line++; + } +} + +gint sort_perf(gconstpointer p1, gconstpointer p2, gpointer key) +{ + struct processtop *n1 = *(struct processtop **) p1; + struct processtop *n2 = *(struct processtop **) p2; + + struct perfcounter *tmp1, *tmp2; + unsigned long totaln2 = 0; + unsigned long totaln1 = 0; + + if (!key) + return 0; + + tmp1 = g_hash_table_lookup(n1->perf, key); + if (!tmp1) + totaln1 = 0; + else + totaln1 = tmp1->count; + + tmp2 = g_hash_table_lookup(n2->perf, key); + if (!tmp2) + totaln2 = 0; + else + totaln2 = tmp2->count; + + if (totaln1 < totaln2) + return 1; + if (totaln1 == totaln2) { + totaln1 = n1->tid; + totaln2 = n2->tid; + if (totaln1 < totaln2) + return 1; + return -1; + } + return -1; +} + +void print_key_title(char *key, int line) +{ + wattron(center, A_BOLD); + mvwprintw(center, line, 1, "%s\t", key); + wattroff(center, A_BOLD); +} + +void update_process_details() +{ + unsigned long elapsed; + double maxcputime; + struct processtop *tmp = find_process_tid(data, selected_tid, selected_comm); + + set_window_title(center, "Process details"); + + + elapsed = data->end - data->start; + maxcputime = elapsed * data->cpu_table->len / 100.0; + + print_key_title("Name", 1); + wprintw(center, "%s", selected_comm); + print_key_title("TID", 2); + wprintw(center, "%d", selected_tid); + if (!tmp) { + print_key_title("Does not exit at this time", 3); + return; + } + + print_key_title("PID", 3); + wprintw(center, "%d", tmp->pid); + print_key_title("PPID", 4); + wprintw(center, "%d", tmp->ppid); + print_key_title("CPU", 5); + wprintw(center, "%1.2f %%", tmp->totalcpunsec/maxcputime); +} + +void update_perf() +{ + int i, j; + int nblinedisplayed = 0; + int current_line = 0; + struct processtop *tmp; + int header_offset = 2; + int perf_row = 40; + struct perfcounter *perfn1, *perfn2; + GList *perflist; + char *perf_key = NULL; + int value; + + set_window_title(center, "Perf Top"); + wattron(center, A_BOLD); + mvwprintw(center, 1, 1, "PID"); + mvwprintw(center, 1, 11, "TID"); + mvwprintw(center, 1, 22, "NAME"); + + perf_row = 40; + perflist = g_list_first(g_hash_table_get_keys(data->perf_list)); + while (perflist) { + perfn1 = g_hash_table_lookup(data->perf_list, perflist->data); + /* + 6 to strip the "_perf_" prefix */ + if (perfn1->visible) { + mvwprintw(center, 1, perf_row, "%s", + (char *) perflist->data + 6); + perf_row += 20; + } + if (perfn1->sort) { + perf_key = (char *) perflist->data; + } + perflist = g_list_next(perflist); + } + wattroff(center, A_BOLD); + + g_ptr_array_sort_with_data(data->process_table, sort_perf, perf_key); + for (i = 0; i < data->process_table->len && + nblinedisplayed < max_center_lines; i++) { + GList *perf_keys; + tmp = g_ptr_array_index(data->process_table, i); + + if (current_line == selected_line) { + selected_process = tmp; + wattron(center, COLOR_PAIR(5)); + mvwhline(center, current_line + header_offset, 1, ' ', COLS-3); + } + + mvwprintw(center, current_line + header_offset, 1, "%d", tmp->pid); + mvwprintw(center, current_line + header_offset, 11, "%d", tmp->tid); + mvwprintw(center, current_line + header_offset, 22, "%s", tmp->comm); + + /* FIXME : sometimes there is a segfault here, I have no idea why :-( */ + perf_keys = g_hash_table_get_keys(data->perf_list); + if (perf_keys) + perflist = g_list_first(perf_keys); + else + perflist = NULL; + + perf_row = 40; + j = 0; + while (perflist) { + j++; + perfn1 = g_hash_table_lookup(data->perf_list, perflist->data); + if (!perfn1) { + perflist = g_list_next(perflist); + continue; + } + if (perfn1->visible) { + perfn2 = g_hash_table_lookup(tmp->perf, perflist->data); + if (perfn2) + value = perfn2->count; + else + value = 0; + mvwprintw(center, current_line + header_offset, perf_row, "%d", value); + perf_row += 20; + } + perflist = g_list_next(perflist); + } + + wattroff(center, COLOR_PAIR(5)); + nblinedisplayed++; + current_line++; + } +} + +void update_fileio() +{ + int i; + int offset; + + set_window_title(center, "IO Top"); + wattron(center, A_BOLD); + mvwprintw(center, 1, 10, "READ"); + mvwprintw(center, 2, 1, "bytes"); + mvwprintw(center, 2, 15, "bytes/sec"); + + mvwprintw(center, 1, 39, "WRITE"); + mvwprintw(center, 2, 33, "bytes"); + mvwprintw(center, 2, 45, "bytes/sec"); + + if (toggle_threads > 0) { + mvwprintw(center, 1, 60, "TGID"); + mvwprintw(center, 1, 70, "PID"); + offset = 8; + } else { + mvwprintw(center, 1, 60, "PID(TGID)"); + offset = 0; + } + mvwprintw(center, 1, 72 + offset, "NAME"); + wattroff(center, A_BOLD); + + for (i = 3; i < LINES - 3 - 8 - 1; i++) { + mvwprintw(center, i, 1, "%d", i*1000); + mvwprintw(center, i, 15, "%dk", i); + mvwprintw(center, i, 28, "| %d", i*2000); + mvwprintw(center, i, 45, "%dk", i*2); + if (toggle_threads > 0) { + mvwprintw(center, i, 57, "| %d", i); + mvwprintw(center, i, 70, "%d", i); + } else { + mvwprintw(center, i, 57, "| %d", i); + } + mvwprintw(center, i, 72 + offset, "process_%d", i); + } +} + +gint sort_by_ret_desc(gconstpointer p1, gconstpointer p2) +{ + struct processtop *n1 = *(struct processtop **)p1; + struct processtop *n2 = *(struct processtop **)p2; + unsigned long totaln1 = n1->iostream->ret_total; + unsigned long totaln2 = n2->iostream->ret_total; + + if (totaln1 < totaln2) + return 1; + if (totaln1 == totaln2) + return 0; + return -1; +} + +void update_iostream() +{ + int i; + int header_offset = 2; + struct processtop *tmp; + int nblinedisplayed = 0; + int current_line = 0; + + set_window_title(center, "IO Top"); + wattron(center, A_BOLD); + mvwprintw(center, 1, 1, "READ (B/s)"); + mvwprintw(center, 1, 20, "WRITE (B/s)"); + + mvwprintw(center, 1, 40, "TOTAL STREAM"); + + mvwprintw(center, 1, 60, "TGID"); + mvwprintw(center, 1, 80, "PID"); + + mvwprintw(center, 1, 92, "NAME"); + wattroff(center, A_BOLD); + + g_ptr_array_sort(data->process_table, sort_by_ret_desc); + + for (i = list_offset; i < data->process_table->len && + nblinedisplayed < max_center_lines; i++) { + tmp = g_ptr_array_index(data->process_table, i); + + if (current_line == selected_line) { + selected_process = tmp; + selected_tid = tmp->tid; + selected_comm = tmp->comm; + wattron(center, COLOR_PAIR(5)); + mvwhline(center, current_line + header_offset, 1, ' ', COLS-3); + } + /* READ (bytes/sec) */ + mvwprintw(center, current_line + header_offset, 1, "%lu", + tmp->iostream->ret_read); + + /* WRITE (bytes/sec) */ + mvwprintw(center, current_line + header_offset, 20, "%lu", + tmp->iostream->ret_write); + + /* TOTAL STREAM */ + if(tmp->iostream->ret_total >= 1000000) + mvwprintw(center, current_line + header_offset, 40, "%lu MB", + tmp->iostream->ret_total/1000000); + else if(tmp->iostream->ret_total >=1000) + mvwprintw(center, current_line + header_offset, 40, "%lu KB", + tmp->iostream->ret_total/1000); + else + mvwprintw(center, current_line + header_offset, 40, "%lu B", + tmp->iostream->ret_total); + /* TGID */ + mvwprintw(center, current_line + header_offset, 60, "%d", tmp->pid); + /* PID */ + mvwprintw(center, current_line + header_offset, 80, "%d", tmp->tid); + /* NAME */ + mvwprintw(center, current_line + header_offset, 92, "%s", tmp->comm); + wattroff(center, COLOR_PAIR(5)); + nblinedisplayed++; + current_line++; + } +} + +void update_current_view() +{ + sem_wait(&update_display_sem); + if (!data) + return; + update_header(); + + werase(center); + box(center, 0, 0); + switch (current_view) { + case cpu: + update_cputop_display(); + break; + case perf: + update_perf(); + break; + case process_details: + update_process_details(); + break; + case fileio: + update_fileio(); + break; + case iostream: + update_iostream(); + break; + case tree: + update_cputop_display(); + break; + default: + break; + } + update_panels(); + doupdate(); + sem_post(&update_display_sem); +} + +void setup_perf_panel() +{ + int size; + if (!data) + return; + if (perf_panel_window) { + del_panel(perf_panel); + delwin(perf_panel_window); + } + size = g_hash_table_size(data->perf_list); + perf_panel_window = create_window(size + 2, 30, 10, 10); + perf_panel = new_panel(perf_panel_window); + perf_panel_visible = 0; + hide_panel(perf_panel); +} + +void update_perf_panel(int line_selected, int toggle_view, int toggle_sort) +{ + int i; + struct perfcounter *perf; + GList *perflist; + + if (!data) + return; + + werase(perf_panel_window); + box(perf_panel_window, 0 , 0); + set_window_title(perf_panel_window, "Perf Preferences "); + wattron(perf_panel_window, A_BOLD); + mvwprintw(perf_panel_window, g_hash_table_size(data->perf_list) + 1, 1, " 's' to sort"); + wattroff(perf_panel_window, A_BOLD); + + if (toggle_sort == 1) { + i = 0; + perflist = g_list_first(g_hash_table_get_keys(data->perf_list)); + while (perflist) { + perf = g_hash_table_lookup(data->perf_list, perflist->data); + if (i != line_selected) + perf->sort = 0; + else + perf->sort = 1; + i++; + perflist = g_list_next(perflist); + } + update_current_view(); + } + + i = 0; + perflist = g_list_first(g_hash_table_get_keys(data->perf_list)); + while (perflist) { + perf = g_hash_table_lookup(data->perf_list, perflist->data); + if (i == line_selected && toggle_view == 1) { + perf->visible = perf->visible == 1 ? 0:1; + update_current_view(); + } + if (i == line_selected) { + wattron(perf_panel_window, COLOR_PAIR(5)); + mvwhline(perf_panel_window, i + 1, 1, ' ', 30 - 2); + } + if (perf->sort == 1) + wattron(perf_panel_window, A_BOLD); + mvwprintw(perf_panel_window, i + 1, 1, "[%c] %s", + perf->visible == 1 ? 'x' : ' ', + (char *) perflist->data + 6); + wattroff(perf_panel_window, A_BOLD); + wattroff(perf_panel_window, COLOR_PAIR(5)); + i++; + perflist = g_list_next(perflist); + } + update_panels(); + doupdate(); +} + + +void toggle_perf_panel(void) +{ + if (perf_panel_visible) { + hide_panel(perf_panel); + perf_panel_visible = 0; + } else { + setup_perf_panel(); + update_perf_panel(perf_line_selected, 0, 0); + show_panel(perf_panel); + perf_panel_visible = 1; + } + update_panels(); + doupdate(); +} + +void display(unsigned int index) +{ + last_display_index = index; + currently_displayed_index = index; + data = g_ptr_array_index(copies, index); + if (!data) + return; + max_elements = data->process_table->len; + update_current_view(); + update_footer(); + update_panels(); + doupdate(); +} + +void pause_display() +{ + toggle_pause = 1; + print_log("Pause"); + sem_wait(&pause_sem); +} + +void resume_display() +{ + toggle_pause = -1; + print_log("Resume"); + sem_post(&pause_sem); +} + +void *handle_keyboard(void *p) +{ + int ch; + while((ch = getch())) { + switch(ch) { + /* Move the cursor and scroll */ + case KEY_DOWN: + if (perf_panel_visible) { + if (perf_line_selected < g_hash_table_size(data->perf_list) - 1) + perf_line_selected++; + update_perf_panel(perf_line_selected, 0, 0); + } else { + if (selected_line < (max_center_lines - 1) && + selected_line < max_elements - 1) { + selected_line++; + selected_in_list++; + } else if (selected_in_list < (max_elements - 1) + && (list_offset < (max_elements - max_center_lines))) { + selected_in_list++; + list_offset++; + } + update_current_view(); + } + break; + case KEY_NPAGE: + if ((selected_line + 10 < max_center_lines - 1) && + ((selected_line + 10) < max_elements - 1)) { + selected_line += 10; + selected_in_list += 10; + } else if (max_elements > max_center_lines) { + selected_line = max_center_lines - 1; + if (selected_in_list + 10 < max_elements - 1) { + selected_in_list += 10; + list_offset += (selected_in_list - max_center_lines + 1); + } + } else if (selected_line + 10 > max_elements) { + selected_line = max_elements - 1; + } + update_current_view(); + break; + case KEY_UP: + if (perf_panel_visible) { + if (perf_line_selected > 0) + perf_line_selected--; + update_perf_panel(perf_line_selected, 0, 0); + } else { + if (selected_line > 0) { + selected_line--; + selected_in_list--; + } else if (selected_in_list > 0 && list_offset > 0) { + selected_in_list--; + list_offset--; + } + update_current_view(); + } + break; + case KEY_PPAGE: + if (selected_line - 10 > 0) + selected_line -= 10; + else + selected_line = 0; + update_current_view(); + break; + + /* Navigate the history with arrows */ + case KEY_LEFT: + if (currently_displayed_index > 0) { + currently_displayed_index--; + print_log("Going back in time"); + } else { + print_log("Cannot rewind, last data is already displayed"); + } + data = g_ptr_array_index(copies, currently_displayed_index); + max_elements = data->process_table->len; + + /* we force to pause the display when moving in time */ + if (toggle_pause < 0) + pause_display(); + + update_current_view(); + update_footer(); + break; + case KEY_RIGHT: + if (currently_displayed_index < last_display_index) { + currently_displayed_index++; + print_log("Going forward in time"); + data = g_ptr_array_index(copies, currently_displayed_index); + max_elements = data->process_table->len; + update_current_view(); + update_footer(); + } else { + print_log("Manually moving forward"); + sem_post(&timer); + /* we force to resume the refresh when moving forward */ + if (toggle_pause > 0) + resume_display(); + } + + break; + case ' ': + if (perf_panel_visible) + update_perf_panel(perf_line_selected, 1, 0); + break; + case 's': + if (perf_panel_visible) + update_perf_panel(perf_line_selected, 0, 1); + break; + + case 13: /* FIXME : KEY_ENTER ?? */ + if (current_view == cpu) { + current_view = process_details; + } + update_current_view(); + break; + + case KEY_F(1): + toggle_tree *= -1; + current_view = cpu; + update_current_view(); + break; + case KEY_F(2): + current_view = cpu; + update_current_view(); + break; + case KEY_F(3): + current_view = perf; + toggle_tree = -1; + update_current_view(); + break; + case KEY_F(4): + current_view = fileio; + toggle_tree = -1; + update_current_view(); + break; + case KEY_F(5): + current_view = netio; + toggle_tree = -1; + update_current_view(); + break; + case KEY_F(6): + current_view = iostream; + toggle_tree = -1; + update_current_view(); + break; + case KEY_F(10): + case 'q': + reset_ncurses(); + break; + case 't': + toggle_threads *= -1; + update_current_view(); + break; + case 'p': + if (toggle_pause < 0) { + pause_display(); + } else { + resume_display(); + } + break; + case 'P': + toggle_perf_panel(); + break; + default: + /* + * commented because it makes the list refresh in different order + * if we sort and there are equal values + if (data) + update_current_view(); + */ + break; + } + update_footer(); + } + return NULL; +} + +void init_ncurses() +{ + sem_init(&update_display_sem, 0, 1); + init_screen(); + + header = create_window(7, COLS - 1, 0, 0); + center = create_window(LINES - 7 - 7, COLS - 1, 7, 0); + status = create_window(MAX_LOG_LINES + 2, COLS - 1, LINES - 7, 0); + footer = create_window(1, COLS - 1, LINES - 1, 0); + + print_log("Starting display"); + + main_panel = new_panel(center); + setup_perf_panel(); + + current_view = cpu; + + basic_header(); + update_footer(); + + pthread_create(&keyboard_thread, NULL, handle_keyboard, (void *)NULL); +} + diff --git a/lttngtop/cursesdisplay.h b/lttngtop/cursesdisplay.h new file mode 100644 index 0000000..6a54252 --- /dev/null +++ b/lttngtop/cursesdisplay.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2011 Julien Desfossez + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation; + * + * 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. + */ + +#ifndef CURSESDISPLAY_H +#define CURSESDISPLAY_H + +#include +#include +#include "common.h" + +enum current_view_list +{ + cpu = 1, + perf, + process_details, + fileio, + netio, + iostream, + tree, +} current_view; + +void display(unsigned int); +void init_ncurses(); +void reset_ncurses(); + +#endif // CURSESDISPLAY_H diff --git a/lttngtop/iostreamtop.c b/lttngtop/iostreamtop.c new file mode 100644 index 0000000..1594df7 --- /dev/null +++ b/lttngtop/iostreamtop.c @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2011 Mathieu Bain + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation; + * + * 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 + +#include "lttngtoptypes.h" +#include "common.h" +#include "iostreamtop.h" + +#include + +int update_iostream_ret(struct lttngtop *ctx, int tid, char *comm, + unsigned long timestamp, int cpu_id, int ret) +{ + struct processtop *tmp; + int err = 0; + + tmp = get_proc(ctx, tid, comm, timestamp); + if ((tmp->iostream->syscall_info != NULL) && (tmp->iostream->syscall_info->cpu_id == cpu_id)) { + if (tmp->iostream->syscall_info->type == __NR_read && ret > 0) { + tmp->iostream->ret_read += ret; + tmp->iostream->ret_total += ret; + } else if(tmp->iostream->syscall_info->type == __NR_write && ret > 0) { + tmp->iostream->ret_write += ret; + tmp->iostream->ret_total += ret; + } else{ + err = -1; + } + free(tmp->iostream->syscall_info); + tmp->iostream->syscall_info = NULL; + } + + return err; +} + +enum bt_cb_ret handle_exit_syscall(struct bt_ctf_event *call_data, + void *private_data) +{ + struct definition *scope; + unsigned long timestamp; + char *comm; + uint64_t ret, tid; + int64_t cpu_id; + + timestamp = bt_ctf_get_timestamp(call_data); + if (timestamp == -1ULL) + goto error; + + scope = bt_ctf_get_top_level_scope(call_data, + BT_STREAM_EVENT_CONTEXT); + comm = bt_ctf_get_char_array(bt_ctf_get_field(call_data, + scope, "_procname")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing procname context info\n"); + goto error; + } + + tid = bt_ctf_get_int64(bt_ctf_get_field(call_data, + scope, "_tid")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing tid context info\n"); + goto error; + } + + scope = bt_ctf_get_top_level_scope(call_data, + BT_EVENT_FIELDS); + ret = bt_ctf_get_int64(bt_ctf_get_field(call_data, + scope, "_ret")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing ret context info\n"); + goto error; + } + + scope = bt_ctf_get_top_level_scope(call_data, + BT_STREAM_PACKET_CONTEXT); + cpu_id = bt_ctf_get_uint64(bt_ctf_get_field(call_data, + scope, "cpu_id")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing cpu_id context info\n"); + goto error; + } + + /* + * if we encounter an exit_syscall and it is not for a syscall read or write + * we just abort the execution of this callback + */ + if ((update_iostream_ret(<tngtop, tid, comm, timestamp, cpu_id, ret)) < 0) + return BT_CB_ERROR_CONTINUE; + + return BT_CB_OK; + +error: + return BT_CB_ERROR_STOP; +} + + +enum bt_cb_ret handle_sys_write(struct bt_ctf_event *call_data, + void *private_data) +{ + struct definition *scope; + struct processtop *tmp; + struct syscalls *syscall_info; + unsigned long timestamp; + uint64_t cpu_id; + char *comm; + int64_t tid; + + timestamp = bt_ctf_get_timestamp(call_data); + if (timestamp == -1ULL) + goto error; + + scope = bt_ctf_get_top_level_scope(call_data, + BT_STREAM_EVENT_CONTEXT); + comm = bt_ctf_get_char_array(bt_ctf_get_field(call_data, + scope, "_procname")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing procname context info\n"); + goto error; + } + + tid = bt_ctf_get_int64(bt_ctf_get_field(call_data, + scope, "_tid")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing tid context info\n"); + goto error; + } + + scope = bt_ctf_get_top_level_scope(call_data, + BT_STREAM_PACKET_CONTEXT); + cpu_id = bt_ctf_get_uint64(bt_ctf_get_field(call_data, + scope, "cpu_id")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing cpu_id context info\n"); + goto error; + } + + syscall_info = malloc(sizeof(struct syscalls)); + syscall_info->cpu_id = cpu_id; + syscall_info->type = __NR_write; + syscall_info->tid = tid; + tmp = get_proc(<tngtop, tid, comm, timestamp); + tmp->iostream->syscall_info = syscall_info; + + return BT_CB_OK; + +error: + return BT_CB_ERROR_STOP; +} + +enum bt_cb_ret handle_sys_read(struct bt_ctf_event *call_data, + void *private_data) +{ + struct processtop *tmp; + struct definition *scope; + struct syscalls * syscall_info; + unsigned long timestamp; + uint64_t cpu_id; + char *comm; + int64_t tid; + + timestamp = bt_ctf_get_timestamp(call_data); + if (timestamp == -1ULL) + goto error; + + scope = bt_ctf_get_top_level_scope(call_data, + BT_STREAM_EVENT_CONTEXT); + comm = bt_ctf_get_char_array(bt_ctf_get_field(call_data, + scope, "_procname")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing procname context info\n"); + goto error; + } + + tid = bt_ctf_get_int64(bt_ctf_get_field(call_data, + scope, "_tid")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing tid context info\n"); + goto error; + } + + scope = bt_ctf_get_top_level_scope(call_data, + BT_STREAM_PACKET_CONTEXT); + cpu_id = bt_ctf_get_uint64(bt_ctf_get_field(call_data, + scope, "cpu_id")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing cpu_id context info\n"); + goto error; + } + + syscall_info = malloc(sizeof(struct syscalls)); + syscall_info->cpu_id = cpu_id; + syscall_info->type = __NR_read; + syscall_info->tid = tid; + tmp = get_proc(<tngtop, tid, comm, timestamp); + tmp->iostream->syscall_info = syscall_info; + + return BT_CB_OK; + +error: + return BT_CB_ERROR_STOP; +} + diff --git a/lttngtop/iostreamtop.h b/lttngtop/iostreamtop.h new file mode 100644 index 0000000..bde4dbf --- /dev/null +++ b/lttngtop/iostreamtop.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2011 Mathieu Bain + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation; + * + * 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. + */ + +#ifndef _IOSTREANTOP_H +#define _IOSTREAMTOP_H + +#include +#include +#include +#include + +/* +#define SYS_READ 1 +#define SYS_WRITE 2 +*/ + +enum bt_cb_ret handle_exit_syscall(struct bt_ctf_event *call_data, + void *private_data); + +enum bt_cb_ret handle_sys_write(struct bt_ctf_event *call_data, + void *private_data); + +enum bt_cb_ret handle_sys_read(struct bt_ctf_event *call_data, + void *private_data); + +#endif /* _IOSTREAMTOP_H */ diff --git a/lttngtop/lttngtop.c b/lttngtop/lttngtop.c new file mode 100644 index 0000000..5d526f3 --- /dev/null +++ b/lttngtop/lttngtop.c @@ -0,0 +1,572 @@ +/* + * Copyright (C) 2011 Julien Desfossez + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation; + * + * 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. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lttngtoptypes.h" +#include "cputop.h" +#include "iostreamtop.h" +#include "cursesdisplay.h" +#include "common.h" + +#define DEFAULT_FILE_ARRAY_SIZE 1 + +const char *opt_input_path; + +struct lttngtop *copy; +pthread_t display_thread; +pthread_t timer_thread; + +unsigned long refresh_display = 1 * NSEC_PER_SEC; +unsigned long last_display_update = 0; +int quit = 0; + +enum { + OPT_NONE = 0, + OPT_HELP, + OPT_LIST, + OPT_VERBOSE, + OPT_DEBUG, + OPT_NAMES, +}; + +static struct poptOption long_options[] = { + /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */ + { "help", 'h', POPT_ARG_NONE, NULL, OPT_HELP, NULL, NULL }, + { NULL, 0, 0, NULL, 0, NULL, NULL }, +}; + +void *refresh_thread(void *p) +{ + while (1) { + sem_wait(&pause_sem); + sem_post(&pause_sem); + sem_post(&timer); + sleep(refresh_display/NSEC_PER_SEC); + } +} + +void *ncurses_display(void *p) +{ + unsigned int current_display_index = 0; + + sem_wait(&bootstrap); + init_ncurses(); + + while (1) { + sem_wait(&timer); + sem_wait(&goodtodisplay); + sem_wait(&pause_sem); + + copy = g_ptr_array_index(copies, current_display_index); + if (copy) + display(current_display_index++); + + sem_post(&goodtoupdate); + sem_post(&pause_sem); + + if (quit) { + reset_ncurses(); + pthread_exit(0); + } + } +} + +/* + * hook on each event to check the timestamp and refresh the display if + * necessary + */ +enum bt_cb_ret check_timestamp(struct bt_ctf_event *call_data, void *private_data) +{ + unsigned long timestamp; + + timestamp = bt_ctf_get_timestamp(call_data); + if (timestamp == -1ULL) + goto error; + + if (last_display_update == 0) + last_display_update = timestamp; + + if (timestamp - last_display_update >= refresh_display) { + sem_wait(&goodtoupdate); + g_ptr_array_add(copies, get_copy_lttngtop(last_display_update, + timestamp)); + sem_post(&goodtodisplay); + sem_post(&bootstrap); + last_display_update = timestamp; + } + return BT_CB_OK; + +error: + fprintf(stderr, "check_timestamp callback error\n"); + return BT_CB_ERROR_STOP; +} + +/* + * get_perf_counter : get or create and return a perf_counter struct for + * either a process or a cpu (only one of the 2 parameters mandatory) + */ +struct perfcounter *get_perf_counter(const char *name, struct processtop *proc, + struct cputime *cpu) +{ + struct perfcounter *ret, *global; + GHashTable *table; + + if (proc) + table = proc->perf; + else if (cpu) + table = cpu->perf; + else + goto error; + + ret = g_hash_table_lookup(table, (gpointer) name); + if (ret) + goto end; + + ret = malloc(sizeof(struct perfcounter)); + memset(ret, 0, sizeof(struct perfcounter)); + /* by default, make it visible in the UI */ + ret->visible = 1; + g_hash_table_insert(table, (gpointer) name, ret); + + global = g_hash_table_lookup(lttngtop.perf_list, (gpointer) name); + if (!global) { + global = malloc(sizeof(struct perfcounter)); + memset(global, 0, sizeof(struct perfcounter)); + memcpy(global, ret, sizeof(struct perfcounter)); + /* by default, sort on the first perf context */ + if (g_hash_table_size(lttngtop.perf_list) == 0) + global->sort = 1; + g_hash_table_insert(lttngtop.perf_list, (gpointer) name, global); + } + +end: + return ret; + +error: + return NULL; +} + +void update_perf_value(struct processtop *proc, struct cputime *cpu, + const char *name, int value) +{ + struct perfcounter *cpu_perf, *process_perf; + + cpu_perf = get_perf_counter(name, NULL, cpu); + if (cpu_perf->count < value) { + process_perf = get_perf_counter(name, proc, NULL); + process_perf->count += value - cpu_perf->count; + cpu_perf->count = value; + } +} + +void extract_perf_counter_scope(struct bt_ctf_event *event, + struct definition *scope, + struct processtop *proc, + struct cputime *cpu) +{ + struct definition const * const *list = NULL; + unsigned int count; + int i, ret; + + if (!scope) + goto end; + + ret = bt_ctf_get_field_list(event, scope, &list, &count); + if (ret < 0) + goto end; + + for (i = 0; i < count; i++) { + const char *name = bt_ctf_field_name(list[i]); + if (strncmp(name, "_perf_", 6) == 0) { + int value = bt_ctf_get_uint64(list[i]); + if (bt_ctf_field_get_error()) + continue; + update_perf_value(proc, cpu, name, value); + } + } + +end: + return; +} + +void update_perf_counter(struct processtop *proc, struct bt_ctf_event *event) +{ + struct definition *scope; + uint64_t cpu_id; + struct cputime *cpu; + + scope = bt_ctf_get_top_level_scope(event, BT_STREAM_PACKET_CONTEXT); + cpu_id = bt_ctf_get_uint64(bt_ctf_get_field(event, scope, "cpu_id")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "[error] get cpu_id\n"); + goto end; + } + cpu = get_cpu(cpu_id); + + scope = bt_ctf_get_top_level_scope(event, BT_STREAM_EVENT_CONTEXT); + extract_perf_counter_scope(event, scope, proc, cpu); + + scope = bt_ctf_get_top_level_scope(event, BT_STREAM_PACKET_CONTEXT); + extract_perf_counter_scope(event, scope, proc, cpu); + + scope = bt_ctf_get_top_level_scope(event, BT_EVENT_CONTEXT); + extract_perf_counter_scope(event, scope, proc, cpu); + +end: + return; +} + +enum bt_cb_ret fix_process_table(struct bt_ctf_event *call_data, + void *private_data) +{ + int pid, tid, ppid; + char *comm; + struct processtop *parent, *child; + struct definition *scope; + unsigned long timestamp; + + /* FIXME : check context pid, tid, ppid and comm */ + + timestamp = bt_ctf_get_timestamp(call_data); + if (timestamp == -1ULL) + goto error; + + scope = bt_ctf_get_top_level_scope(call_data, BT_STREAM_EVENT_CONTEXT); + + pid = bt_ctf_get_int64(bt_ctf_get_field(call_data, scope, "_pid")); + if (bt_ctf_field_get_error()) { +// fprintf(stderr, "Missing pid context info\n"); + goto error; + } + tid = bt_ctf_get_int64(bt_ctf_get_field(call_data, scope, "_tid")); + if (bt_ctf_field_get_error()) { +// fprintf(stderr, "Missing tid context info\n"); + goto error; + } + ppid = bt_ctf_get_int64(bt_ctf_get_field(call_data, scope, "_ppid")); + if (bt_ctf_field_get_error()) { +// fprintf(stderr, "Missing ppid context info\n"); + goto error; + } + comm = bt_ctf_get_char_array(bt_ctf_get_field(call_data, scope, "_procname")); + if (bt_ctf_field_get_error()) { +// fprintf(stderr, "Missing procname context info\n"); + goto error; + } + + /* find or create the current process */ + child = find_process_tid(<tngtop, tid, comm); + if (!child) + child = add_proc(<tngtop, tid, comm, timestamp); + update_proc(child, pid, tid, ppid, comm); + + if (pid != tid) { + /* find or create the parent */ + parent = find_process_tid(<tngtop, pid, comm); + if (!parent) { + parent = add_proc(<tngtop, pid, comm, timestamp); + parent->pid = pid; + } + + /* attach the parent to the current process */ + child->threadparent = parent; + add_thread(parent, child); + } + + update_perf_counter(child, call_data); + + return BT_CB_OK; + +error: + return BT_CB_ERROR_STOP; +} + +void init_lttngtop() +{ + copies = g_ptr_array_new(); + lttngtop.perf_list = g_hash_table_new(g_direct_hash, g_direct_equal); + + sem_init(&goodtodisplay, 0, 0); + sem_init(&goodtoupdate, 0, 1); + sem_init(&timer, 0, 1); + sem_init(&bootstrap, 0, 0); + sem_init(&pause_sem, 0, 1); + sem_init(&end_trace_sem, 0, 0); + + lttngtop.process_table = g_ptr_array_new(); + lttngtop.files_table = g_ptr_array_new(); + lttngtop.cpu_table = g_ptr_array_new(); +} + +void usage(FILE *fd) +{ + +} + +/* + * Return 0 if caller should continue, < 0 if caller should return + * error, > 0 if caller should exit without reporting error. + */ +static int parse_options(int argc, char **argv) +{ + poptContext pc; + int opt, ret = 0; + + if (argc == 1) { + usage(stdout); + return 1; /* exit cleanly */ + } + + pc = poptGetContext(NULL, argc, (const char **) argv, long_options, 0); + poptReadDefaultConfig(pc, 0); + + while ((opt = poptGetNextOpt(pc)) != -1) { + switch (opt) { + case OPT_HELP: + usage(stdout); + ret = 1; /* exit cleanly */ + goto end; + case OPT_LIST: + // list_formats(stdout); + ret = 1; + goto end; + case OPT_VERBOSE: +// babeltrace_verbose = 1; + break; + case OPT_DEBUG: +// babeltrace_debug = 1; + break; + case OPT_NAMES: +// opt_field_names = 1; + break; + default: + ret = -EINVAL; + goto end; + } + } + + opt_input_path = poptGetArg(pc); + if (!opt_input_path) { + ret = -EINVAL; + goto end; + } +end: + if (pc) { + poptFreeContext(pc); + } + return ret; +} + +void iter_trace(struct bt_context *bt_ctx) +{ + struct bt_ctf_iter *iter; + struct bt_iter_pos begin_pos; + struct bt_ctf_event *event; + int ret = 0; + + begin_pos.type = BT_SEEK_BEGIN; + iter = bt_ctf_iter_create(bt_ctx, &begin_pos, NULL); + + /* at each event check if we need to refresh */ + bt_ctf_iter_add_callback(iter, 0, NULL, 0, + check_timestamp, + NULL, NULL, NULL); + /* at each event, verify the status of the process table */ + bt_ctf_iter_add_callback(iter, 0, NULL, 0, + fix_process_table, + NULL, NULL, NULL); + /* to handle the scheduling events */ + bt_ctf_iter_add_callback(iter, + g_quark_from_static_string("sched_switch"), + NULL, 0, handle_sched_switch, NULL, NULL, NULL); + /* to clean up the process table */ + bt_ctf_iter_add_callback(iter, + g_quark_from_static_string("sched_process_free"), + NULL, 0, handle_sched_process_free, NULL, NULL, NULL); + + /* for IO top */ + bt_ctf_iter_add_callback(iter, + g_quark_from_static_string("exit_syscall"), + NULL, 0, handle_exit_syscall, NULL, NULL, NULL); + bt_ctf_iter_add_callback(iter, + g_quark_from_static_string("sys_write"), + NULL, 0, handle_sys_write, NULL, NULL, NULL); + bt_ctf_iter_add_callback(iter, + g_quark_from_static_string("sys_read"), + NULL, 0, handle_sys_read, NULL, NULL, NULL); + while ((event = bt_ctf_iter_read_event(iter)) != NULL) { + ret = bt_iter_next(bt_ctf_get_iter(iter)); + if (ret < 0) + goto end_iter; + } + + /* block until quit, we reached the end of the trace */ + sem_wait(&end_trace_sem); + +end_iter: + bt_iter_destroy(bt_ctf_get_iter(iter)); +} + +/* + * bt_context_add_traces_recursive: Open a trace recursively + * (copied from BSD code in converter/babeltrace.c) + * + * Find each trace present in the subdirectory starting from the given + * path, and add them to the context. The packet_seek parameter can be + * NULL: this specify to use the default format packet_seek. + * + * Return: 0 on success, nonzero on failure. + * Unable to open toplevel: failure. + * Unable to open some subdirectory or file: warn and continue; + */ +int bt_context_add_traces_recursive(struct bt_context *ctx, const char *path, + const char *format_str, + void (*packet_seek)(struct stream_pos *pos, + size_t offset, int whence)) +{ + FTS *tree; + FTSENT *node; + GArray *trace_ids; + char lpath[PATH_MAX]; + char * const paths[2] = { lpath, NULL }; + int ret; + + /* + * Need to copy path, because fts_open can change it. + * It is the pointer array, not the strings, that are constant. + */ + strncpy(lpath, path, PATH_MAX); + lpath[PATH_MAX - 1] = '\0'; + + tree = fts_open(paths, FTS_NOCHDIR | FTS_LOGICAL, 0); + if (tree == NULL) { + fprintf(stderr, "[error] [Context] Cannot traverse \"%s\" for reading.\n", + path); + return -EINVAL; + } + + trace_ids = g_array_new(FALSE, TRUE, sizeof(int)); + + while ((node = fts_read(tree))) { + int dirfd, metafd; + + if (!(node->fts_info & FTS_D)) + continue; + + dirfd = open(node->fts_accpath, 0); + if (dirfd < 0) { + fprintf(stderr, "[error] [Context] Unable to open trace " + "directory file descriptor.\n"); + ret = dirfd; + goto error; + } + metafd = openat(dirfd, "metadata", O_RDONLY); + if (metafd < 0) { + ret = close(dirfd); + if (ret < 0) { + perror("close"); + goto error; + } + } else { + int trace_id; + + ret = close(metafd); + if (ret < 0) { + perror("close"); + goto error; + } + ret = close(dirfd); + if (ret < 0) { + perror("close"); + goto error; + } + + trace_id = bt_context_add_trace(ctx, + node->fts_accpath, format_str, + packet_seek, NULL, NULL); + if (trace_id < 0) { + fprintf(stderr, "[error] [Context] opening trace \"%s\" from %s " + "for reading.\n", node->fts_accpath, path); + ret = trace_id; + goto error; + } + g_array_append_val(trace_ids, trace_id); + } + } + + g_array_free(trace_ids, TRUE); + return 0; + +error: + return ret; +} + +int main(int argc, char **argv) +{ + int ret; + struct bt_context *bt_ctx = NULL; + + ret = parse_options(argc, argv); + if (ret < 0) { + fprintf(stdout, "Error parsing options.\n\n"); + usage(stdout); + exit(EXIT_FAILURE); + } else if (ret > 0) { + exit(EXIT_SUCCESS); + } + + init_lttngtop(); + + bt_ctx = bt_context_create(); + ret = bt_context_add_traces_recursive(bt_ctx, opt_input_path, "ctf", NULL); + if (ret < 0) { + printf("[error] Opening the trace\n"); + goto end; + } + + pthread_create(&display_thread, NULL, ncurses_display, (void *) NULL); + pthread_create(&timer_thread, NULL, refresh_thread, (void *) NULL); + + iter_trace(bt_ctx); + + quit = 1; + pthread_join(display_thread, NULL); + +end: + return 0; +} diff --git a/lttngtop/lttngtoptypes.h b/lttngtop/lttngtoptypes.h new file mode 100644 index 0000000..f10a684 --- /dev/null +++ b/lttngtop/lttngtoptypes.h @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2011 Julien Desfossez + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation; + * + * 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. + */ + +#ifndef LTTNGTOPTYPES_H +#define LTTNGTOPTYPES_H + +#include + +struct lttngtop { + GPtrArray *process_table; /* struct processtop */ + GPtrArray *files_table; /* struct files */ + GPtrArray *cpu_table; /* struct cputime */ + GHashTable *perf_list; /* struct perfcounter */ + unsigned long start; + unsigned long end; + unsigned int nbproc; + unsigned int nbnewproc; + unsigned int nbdeadproc; + unsigned int nbthreads; + unsigned int nbnewthreads; + unsigned int nbdeadthreads; + unsigned int nbfiles; + unsigned int nbnewfiles; + unsigned int nbclosedfiles; +} lttngtop; + +struct processtop { + unsigned int puuid; + int pid; + char *comm; + int tid; + int ppid; + int oldpid; + int oldtid; + int oldppid; + unsigned long birth; + unsigned long death; + unsigned long lastactivity; + GPtrArray *process_files_table; + GPtrArray *threads; + GHashTable *perf; + struct processtop *threadparent; + unsigned long totalfileread; + unsigned long totalfilewrite; + unsigned long totalcpunsec; + unsigned long threadstotalcpunsec; + /* IO speed for this process */ + struct iostream *iostream; +}; + +struct perfcounter +{ + unsigned long count; + int visible; + int sort; +}; + +struct cputime { + guint id; + struct processtop *current_task; + unsigned long task_start; + GHashTable *perf; +}; + +/* + * used for "relative seeks" (with fd, for example fs.lseek) + * and for "absolute seeks" (events occuring on a device without + * any link to a particular process) + */ +struct seeks { + unsigned long offset; + unsigned long count; +}; + +struct ioctls { + unsigned int command; + unsigned long count; +}; + +struct files { + struct processtop *ref; + unsigned int fuuid; + int fd; + char *name; + int oldfd; + int device; + int openmode; + unsigned long openedat; + unsigned long closedat; + unsigned long lastaccess; + unsigned long read; + unsigned long write; + unsigned long nbpoll; + unsigned long nbselect; + unsigned long nbopen; + unsigned long nbclose; + //struct *seeks; /* relative seeks inside the file */ + //struct *ioctls; + /* XXX : average wait time */ +}; + +struct sockets { + int fd; + int parent_fd; /* on accept a new fd is created from the bound socket */ + int family; + int type; + int protocol; + int sock_address; + unsigned long openedat; + unsigned long closedat; + unsigned long bind_address; + unsigned long remote_address; + //struct *sock_options; +}; + +struct sock_options { + int name; + int value; +}; + +struct vmas { + unsigned long start; + unsigned long end; + unsigned long flags; + unsigned long prot; + char *description; /* filename or description if possible (stack, heap) */ + unsigned long page_faults; +}; + +struct syscalls { + unsigned int id; + unsigned long count; + unsigned int cpu_id; + unsigned int type; + unsigned int tid; +}; + +struct signals { + int dest_pid; + int id; + unsigned long count; +}; + +struct iostream { + struct syscalls *syscall_info; /* NULL if there is no waiting for an exit_syscall */ + unsigned long ret_read; /* value returned by an I/O syscall_exit for a sys_read*/ + unsigned long ret_write; /* value returned by an I/O syscall_exit for a sys_write*/ + unsigned long ret_total; +}; + +#endif /* LTTNGTOPTYPES_H */