libvips/libvips/iofuncs/gate.c
John Cupitt f9f717a843 add VIPS_ONCE and use it everywhere
Lovell's idea: save the function call in the most common case
2017-11-23 08:43:08 +00:00

398 lines
9.3 KiB
C

/* gate.c --- thread profiling
*
* Written on: 18 nov 13
*/
/*
This file is part of VIPS.
VIPS is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser 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
*/
/*
These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
*/
/* Very verbose.
#define VIPS_DEBUG_RED
*/
/*
#define VIPS_DEBUG
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <vips/intl.h>
#include <vips/vips.h>
#include <vips/internal.h>
#include <vips/debug.h>
#define VIPS_GATE_SIZE (1000)
/* A set of timing records. i is the index of the next slot we fill.
*/
typedef struct _VipsThreadGateBlock {
struct _VipsThreadGateBlock *prev;
gint64 time[VIPS_GATE_SIZE];
int i;
} VipsThreadGateBlock;
/* What we track for each gate-name.
*/
typedef struct _VipsThreadGate {
const char *name;
VipsThreadGateBlock *start;
VipsThreadGateBlock *stop;
} VipsThreadGate;
/* One of these in per-thread private storage.
*/
typedef struct _VipsThreadProfile {
/*< private >*/
const char *name;
GThread *thread;
GHashTable *gates;
VipsThreadGate *memory;
} VipsThreadProfile;
gboolean vips__thread_profile = FALSE;
static GPrivate *vips_thread_profile_key = NULL;
static FILE *vips__thread_fp = NULL;;
/**
* vips_profile_set:
* @profile: %TRUE to enable profile recording
*
* If set, vips will record profiling information, and dump it on program
* exit. These profiles can be analysed with the `vipsprofile` program.
*/
void
vips_profile_set( gboolean profile )
{
vips__thread_profile = profile;
}
static void
vips_thread_gate_block_save( VipsThreadGateBlock *block, FILE *fp )
{
int i;
for( i = block->i - 1; i >= 0; i-- )
fprintf( fp, "%" G_GINT64_FORMAT " ", block->time[i] );
fprintf( fp, "\n" );
if( block->prev )
vips_thread_gate_block_save( block->prev, fp );
}
static void
vips_thread_profile_save_gate( VipsThreadGate *gate, FILE *fp )
{
if( gate->start->i ||
gate->start->prev ) {
fprintf( fp, "gate: %s\n", gate->name );
fprintf( fp, "start:\n" );
vips_thread_gate_block_save( gate->start, fp );
fprintf( fp, "stop:\n" );
vips_thread_gate_block_save( gate->stop, fp );
}
}
static void
vips_thread_profile_save_cb( gpointer key, gpointer value, gpointer data )
{
VipsThreadGate *gate = (VipsThreadGate *) value;
FILE *fp = (FILE *) data;
vips_thread_profile_save_gate( gate, fp );
}
static void
vips_thread_profile_save( VipsThreadProfile *profile )
{
g_mutex_lock( vips__global_lock );
VIPS_DEBUG_MSG( "vips_thread_profile_save: %s\n", profile->name );
if( !vips__thread_fp ) {
vips__thread_fp =
vips__file_open_write( "vips-profile.txt", TRUE );
if( !vips__thread_fp ) {
g_mutex_unlock( vips__global_lock );
g_warning( "unable to create profile log" );
return;
}
printf( "recording profile in vips-profile.txt\n" );
}
fprintf( vips__thread_fp, "thread: %s (%p)\n", profile->name, profile );
g_hash_table_foreach( profile->gates,
vips_thread_profile_save_cb, vips__thread_fp );
vips_thread_profile_save_gate( profile->memory, vips__thread_fp );
g_mutex_unlock( vips__global_lock );
}
static void
vips_thread_gate_block_free( VipsThreadGateBlock *block )
{
VIPS_FREEF( vips_thread_gate_block_free, block->prev );
VIPS_FREE( block );
}
static void
vips_thread_gate_free( VipsThreadGate *gate )
{
VIPS_FREEF( vips_thread_gate_block_free, gate->start );
VIPS_FREEF( vips_thread_gate_block_free, gate->stop );
VIPS_FREE( gate );
}
static void
vips_thread_profile_free( VipsThreadProfile *profile )
{
VIPS_DEBUG_MSG( "vips_thread_profile_free: %s\n", profile->name );
VIPS_FREEF( g_hash_table_destroy, profile->gates );
VIPS_FREEF( vips_thread_gate_free, profile->memory );
VIPS_FREE( profile );
}
void
vips__thread_profile_stop( void )
{
if( vips__thread_profile )
VIPS_FREEF( fclose, vips__thread_fp );
}
static void
vips__thread_profile_init_cb( VipsThreadProfile *profile )
{
/* We only come here if vips_thread_shutdown() was not called for this
* thread. Do our best to clean up.
*
* GPrivate has stopped working, be careful not to touch that.
*
* Don't try to save: we must free all mem before saving and we
* probably haven't done that because vips_thread_shutdown() has not
* been called.
*/
if( vips__thread_profile )
g_warning( "discarding unsaved state for thread %p --- "
"call vips_thread_shutdown() for this thread",
profile->thread );
vips_thread_profile_free( profile );
}
static void
vips__thread_profile_init( void )
{
#ifdef HAVE_PRIVATE_INIT
static GPrivate private =
G_PRIVATE_INIT( (GDestroyNotify) vips__thread_profile_init_cb );
vips_thread_profile_key = &private;
#else
if( !vips_thread_profile_key )
vips_thread_profile_key = g_private_new(
(GDestroyNotify) vips__thread_profile_init_cb );
#endif
}
static VipsThreadGate *
vips_thread_gate_new( const char *gate_name )
{
VipsThreadGate *gate;
gate = g_new( VipsThreadGate, 1 );
gate->name = gate_name;
gate->start = g_new0( VipsThreadGateBlock, 1 );
gate->stop = g_new0( VipsThreadGateBlock, 1 );
return( gate );
}
void
vips__thread_profile_attach( const char *thread_name )
{
static GOnce once = G_ONCE_INIT;
VipsThreadProfile *profile;
VIPS_ONCE( &once, (GThreadFunc) vips__thread_profile_init, NULL );
VIPS_DEBUG_MSG( "vips__thread_profile_attach: %s\n", thread_name );
g_assert( !g_private_get( vips_thread_profile_key ) );
profile = g_new( VipsThreadProfile, 1 );
profile->name = thread_name;
profile->gates = g_hash_table_new_full(
g_direct_hash, g_str_equal,
NULL, (GDestroyNotify) vips_thread_gate_free );
profile->memory = vips_thread_gate_new( "memory" );
g_private_set( vips_thread_profile_key, profile );
}
static VipsThreadProfile *
vips_thread_profile_get( void )
{
return( g_private_get( vips_thread_profile_key ) );
}
/* This usually happens automatically when a thread shuts down, see
* vips__thread_profile_init() where we set a GDestroyNotify, but will not
* happen for the main thread.
*
* Shut down any stats on the main thread with this, see vips_shutdown()
*/
void
vips__thread_profile_detach( void )
{
VipsThreadProfile *profile;
VIPS_DEBUG_MSG( "vips__thread_profile_detach:\n" );
if( (profile = vips_thread_profile_get()) ) {
if( vips__thread_profile )
vips_thread_profile_save( profile );
vips_thread_profile_free( profile );
g_private_set( vips_thread_profile_key, NULL );
}
}
static void
vips_thread_gate_block_add( VipsThreadGateBlock **block )
{
VipsThreadGateBlock *new_block;
new_block = g_new0( VipsThreadGateBlock, 1 );
new_block->prev = *block;
*block = new_block;
}
static gint64
vips_get_time( void )
{
#ifdef HAVE_MONOTONIC_TIME
return( g_get_monotonic_time() );
#else
GTimeVal time;
g_get_current_time( &time );
return( (gint64) time.tv_usec );
#endif
}
void
vips__thread_gate_start( const char *gate_name )
{
VipsThreadProfile *profile;
VIPS_DEBUG_MSG_RED( "vips__thread_gate_start: %s\n", gate_name );
if( (profile = vips_thread_profile_get()) ) {
gint64 time = vips_get_time();
VipsThreadGate *gate;
if( !(gate =
g_hash_table_lookup( profile->gates, gate_name )) ) {
gate = vips_thread_gate_new( gate_name );
g_hash_table_insert( profile->gates,
(char *) gate_name, gate );
}
if( gate->start->i >= VIPS_GATE_SIZE )
vips_thread_gate_block_add( &gate->start );
gate->start->time[gate->start->i++] = time;
VIPS_DEBUG_MSG_RED( "\t %" G_GINT64_FORMAT "\n", time );
}
}
void
vips__thread_gate_stop( const char *gate_name )
{
VipsThreadProfile *profile;
VIPS_DEBUG_MSG_RED( "vips__thread_gate_stop: %s\n", gate_name );
if( (profile = vips_thread_profile_get()) ) {
gint64 time = vips_get_time();
VipsThreadGate *gate;
if( !(gate =
g_hash_table_lookup( profile->gates, gate_name )) ) {
gate = vips_thread_gate_new( gate_name );
g_hash_table_insert( profile->gates,
(char *) gate_name, gate );
}
if( gate->stop->i >= VIPS_GATE_SIZE )
vips_thread_gate_block_add( &gate->stop );
gate->stop->time[gate->stop->i++] = time;
VIPS_DEBUG_MSG_RED( "\t %" G_GINT64_FORMAT "\n", time );
}
}
/* Record a malloc() or free(). Use -ve numbers for free.
*/
void
vips__thread_malloc_free( gint64 size )
{
VipsThreadProfile *profile;
VIPS_DEBUG_MSG_RED( "vips__thread_malloc_free: %zd\n", size );
#ifdef VIPS_DEBUG
if( !(profile = vips_thread_profile_get()) )
printf( "argh no block to record free() in!\n" );
#endif /*VIPS_DEBUG*/
if( (profile = vips_thread_profile_get()) ) {
gint64 time = vips_get_time();
VipsThreadGate *gate = profile->memory;
if( gate->start->i >= VIPS_GATE_SIZE ) {
vips_thread_gate_block_add( &gate->start );
vips_thread_gate_block_add( &gate->stop );
}
gate->start->time[gate->start->i++] = time;
gate->stop->time[gate->stop->i++] = size;
}
}