Skip to content

Commit

Permalink
Merge pull request #87 from P403n1x87/devel
Browse files Browse the repository at this point in the history
Release v3.1.0
  • Loading branch information
P403n1x87 authored Aug 18, 2021
2 parents 6cc35fd + 8a7729c commit 3e8b25f
Show file tree
Hide file tree
Showing 23 changed files with 480 additions and 37 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:
export PATH="/c/Program Files (x86)/`ls /c/Program\ Files\ \(x86\) | grep \"[wW]i[xX] [tT]oolset\"`/bin:$PATH"
export VERSION=$(cat src/austin.h | sed -r -n "s/.*VERSION[ ]+\"(.+)\"/\1/p")
gcc -s -Wall -O3 -Os -o src/austin src/*.c -lpsapi -lntdll
gcc -static -s -Wall -O3 -Os -o src/austin src/*.c -lpsapi -lntdll
git checkout "packaging/msi"
git checkout master
Expand Down
7 changes: 7 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
2021-08-18 v3.1.0

Added garbage collection state sampling for Python 3.7 onward.

Bugfix: the MinGW libwinpthread library is now linked statically on Windows.


2021-06-13 v3.0.0

Added pipe mode.
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ Austin -- A frame stack sampler for Python.
-e, --exclude-empty Do not output samples of threads with no frame
stacks.
-f, --full Produce the full set of metrics (time +mem -mem).
-g, --gc Sample the garbage collector state.
-i, --interval=n_us Sampling interval in microseconds (default is
100). Accepted units: s, ms, us.
-m, --memory Profile memory usage.
Expand Down Expand Up @@ -367,6 +368,17 @@ Austin can be told to profile multi-process applications with the `-C` or
process.


## Garbage Collector Sampling

Austin can sample the Python garbage collector state for application running
with Python 3.7 and later versions. If the `-g`/`--gc` option is passed, Austin
will append `:GC:` at the end of each collected frame stack whenver the
garbage collector is in the collecting state. This gives you a measure of how
*busy* the Python GC is during a run.

*Since Austin 3.1.0*.


## Logging

Austin uses `syslog` on Linux and macOS, and `%TEMP%\austin.log` on Windows
Expand Down
2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.

AC_PREREQ([2.69])
AC_INIT([austin], [3.0.0], [https://github.com/p403n1x87/austin/issues])
AC_INIT([austin], [3.1.0], [https://github.com/p403n1x87/austin/issues])
AC_CONFIG_SRCDIR([config.h.in])
AC_CONFIG_HEADERS([config.h])
AM_INIT_AUTOMAKE
Expand Down
2 changes: 1 addition & 1 deletion snap/snapcraft.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: austin
version: '3.0.0+git'
version: '3.1.0+git'
summary: A Python frame stack sampler for CPython
description: |
Austin is a Python frame stack sampler for CPython written in pure C. It
Expand Down
14 changes: 14 additions & 0 deletions src/argparse.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ parsed_args_t pargs = {
/* children */ 0,
/* exposure */ 0,
/* pipe */ 0,
/* gc */ 0,
};

static int exec_arg = 0;
Expand Down Expand Up @@ -217,6 +218,10 @@ static struct argp_option options[] = {
"pipe", 'P', NULL, 0,
"Pipe mode. Use when piping Austin output."
},
{
"gc", 'g', NULL, 0,
"Sample the garbage collector state."
},
#ifndef PL_LINUX
{
"help", '?', NULL
Expand Down Expand Up @@ -322,6 +327,10 @@ parse_opt (int key, char *arg, struct argp_state *state)
pargs.pipe = 1;
break;

case 'g':
pargs.gc = 1;
break;

case ARGP_KEY_ARG:
case ARGP_KEY_END:
if (pargs.attach_pid != 0 && exec_arg != 0)
Expand Down Expand Up @@ -464,6 +473,7 @@ static const char * help_msg = \
" -e, --exclude-empty Do not output samples of threads with no frame\n"
" stacks.\n"
" -f, --full Produce the full set of metrics (time +mem -mem).\n"
" -g, --gc Sample the garbage collector state.\n"
" -i, --interval=n_us Sampling interval in microseconds (default is\n"
" 100). Accepted units: s, ms, us.\n"
" -m, --memory Profile memory usage.\n"
Expand Down Expand Up @@ -617,6 +627,10 @@ cb(const char opt, const char * arg) {
pargs.pipe = 1;
break;

case 'g':
pargs.gc = 1;
break;

case '?':
puts(help_msg);
exit(0);
Expand Down
1 change: 1 addition & 0 deletions src/argparse.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ typedef struct {
int children;
ctime_t exposure;
int pipe;
int gc;
} parsed_args_t;


Expand Down
7 changes: 5 additions & 2 deletions src/austin.1
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.6.
.TH AUSTIN "1" "June 2021" "austin 3.0.0" "User Commands"
.TH AUSTIN "1" "August 2021" "austin 3.1.0" "User Commands"
.SH NAME
austin \- manual page for austin 3.0.0
austin \- manual page for austin 3.1.0
.SH SYNOPSIS
.B austin
[\fI\,OPTION\/\fR...] \fI\,command \/\fR[\fI\,ARG\/\fR...]
Expand All @@ -21,6 +21,9 @@ stacks.
\fB\-f\fR, \fB\-\-full\fR
Produce the full set of metrics (time +mem \fB\-mem\fR).
.TP
\fB\-g\fR, \fB\-\-gc\fR
Sample the garbage collector state.
.TP
\fB\-i\fR, \fB\-\-interval\fR=\fI\,n_us\/\fR
Sampling interval in microseconds (default is
100). Accepted units: s, ms, us.
Expand Down
6 changes: 5 additions & 1 deletion src/austin.c
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,11 @@ int main(int argc, char ** argv) {
austin_errno = EOK;

// Log sampling metrics
NL;meta("duration: %lu", stats_duration());
NL;
meta("duration: %lu", stats_duration());
if (pargs.gc) {
meta("gc: %lu", _gc_time);
}

stats_log_metrics();NL;

Expand Down
2 changes: 1 addition & 1 deletion src/austin.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@
#define AUSTIN_H

#define PROGRAM_NAME "austin"
#define VERSION "3.0.0"
#define VERSION "3.1.0"

#endif
1 change: 1 addition & 0 deletions src/linux/py_thread.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.

#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
Expand Down
38 changes: 33 additions & 5 deletions src/py_proc.c
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ _py_proc__check_interp_state(py_proc_t * self, void * raddr) {
if (py_proc__get_type(self, raddr, is))
return OUT_OF_BOUND;

if (py_proc__get_type(self, is.tstate_head, tstate_head)) {
if (py_proc__get_type(self, V_FIELD(void *, is, py_is, o_tstate_head), tstate_head)) {
log_t(
"Cannot copy PyThreadState head at %p from PyInterpreterState instance",
is.tstate_head
Expand All @@ -347,7 +347,7 @@ _py_proc__check_interp_state(py_proc_t * self, void * raddr) {
);

// As an extra sanity check, verify that the thread state is valid
raddr_t thread_raddr = { .pid = PROC_REF, .addr = is.tstate_head };
raddr_t thread_raddr = { .pid = PROC_REF, .addr = V_FIELD(void *, is, py_is, o_tstate_head) };
py_thread_t thread;
if (fail(py_thread__fill_from_raddr(&thread, &thread_raddr, self))) {
log_d("Failed to fill thread structure");
Expand All @@ -361,6 +361,11 @@ _py_proc__check_interp_state(py_proc_t * self, void * raddr) {

log_d("Stack trace constructed from possible interpreter state");

if (py_v->major == 3 && py_v->minor >= 9) {
self->gc_state_raddr = (void *) (((char *) raddr) + py_v->py_is.o_gc);
log_d("GC runtime state @ %p", self->gc_state_raddr);
}

SUCCESS;
}

Expand Down Expand Up @@ -475,6 +480,10 @@ _py_proc__deref_interp_head(py_proc_t * self) {
FAIL;
}
interp_head_raddr = V_FIELD(void *, py_runtime, py_runtime, o_interp_head);
if (py_v->major == 3 && py_v->minor < 9) {
self->gc_state_raddr = self->py_runtime_raddr + py_v->py_runtime.o_gc;
log_d("GC runtime state @ %p", self->gc_state_raddr);
}
}
else if (self->interp_head_raddr != NULL) {
if (py_proc__get_type(self, self->interp_head_raddr, interp_head_raddr)) {
Expand Down Expand Up @@ -706,7 +715,8 @@ _py_proc__run(py_proc_t * self, int try_once) {
if (
self->tstate_curr_raddr == NULL &&
self->py_runtime_raddr == NULL &&
self->interp_head_raddr == NULL
self->interp_head_raddr == NULL &&
self->gc_state_raddr == NULL
)
log_w("No remote symbol references have been set.");
#endif
Expand Down Expand Up @@ -746,6 +756,7 @@ py_proc_new() {
return NULL;

py_proc->min_raddr = (void *) -1;
py_proc->gc_state_raddr = NULL;

// Pre-hash symbol names
if (_dynsym_hash_array[0] == 0) {
Expand Down Expand Up @@ -1069,6 +1080,22 @@ _py_proc__get_memory_delta(py_proc_t * self) {
}


// ----------------------------------------------------------------------------
int
py_proc__is_gc_collecting(py_proc_t * self) {
if (!isvalid(self->gc_state_raddr))
return FALSE;

GCRuntimeState gc_state;
if (fail(py_proc__get_type(self, self->gc_state_raddr, gc_state))) {
log_d("Failed to get GC runtime state");
return -1;
}

return V_FIELD(int, gc_state, py_gc, o_collecting);
}


// ----------------------------------------------------------------------------
int
py_proc__sample(py_proc_t * self) {
Expand All @@ -1080,8 +1107,9 @@ py_proc__sample(py_proc_t * self) {
if (fail(py_proc__get_type(self, self->is_raddr, is)))
FAIL;

if (is.tstate_head != NULL) {
raddr_t raddr = { .pid = PROC_REF, .addr = is.tstate_head };
void * tstate_head = V_FIELD(void *, is, py_is, o_tstate_head);
if (isvalid(tstate_head)) {
raddr_t raddr = { .pid = PROC_REF, .addr = tstate_head };
py_thread_t py_thread;
if (fail(py_thread__fill_from_raddr(&py_thread, &raddr, self)))
FAIL;
Expand Down
15 changes: 15 additions & 0 deletions src/py_proc.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ typedef struct {
void * tstate_curr_raddr;
void * py_runtime_raddr;
void * interp_head_raddr;
void * gc_state_raddr;

void * is_raddr;

Expand Down Expand Up @@ -148,6 +149,20 @@ int
py_proc__is_python(py_proc_t *);


/**
* Check whether the GC is collecting for the given process.
*
* NOTE: This method makes sense only for Python>=3.7.
*
* @param py_proc_t * the process object.
*
* @return TRUE if the GC is collecting, FALSE otherwise.
*
*/
int
py_proc__is_gc_collecting(py_proc_t *);


/**
* Sample the frame stack of each thread of the given Python process.
*
Expand Down
6 changes: 5 additions & 1 deletion src/py_thread.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
#include "logging.h"
#include "mem.h"
#include "platform.h"
#include "pthread.h"
#include "timing.h"
#include "version.h"

Expand Down Expand Up @@ -489,6 +488,11 @@ py_thread__print_collapsed_stack(py_thread_t * self, ctime_t time_delta, ssize_t
}
}

if (pargs.gc && py_proc__is_gc_collecting(self->proc) == TRUE) {
fprintf(pargs.output_file, ";:GC:");
stats_gc_time(time_delta);
}

// Finish off sample with the metric(s)
if (pargs.full) {
fprintf(pargs.output_file, " " TIME_METRIC METRIC_SEP IDLE_METRIC METRIC_SEP MEM_METRIC "\n",
Expand Down
Loading

0 comments on commit 3e8b25f

Please sign in to comment.