2015-08-05 12:40:00 +03:00
/*
2016-06-30 23:46:43 +03:00
* Copyright ( C ) 2016 Red Hat , Inc . All rights reserved .
*
* _stats_get_extents_for_file ( ) based in part on filefrag_fiemap ( ) from
* e2fsprogs / misc / filefrag . c . Copyright 2003 by Theodore Ts ' o .
2015-08-05 12:40:00 +03:00
*
* This file is part of the device - mapper userspace tools .
*
* This copyrighted material is made available to anyone wishing to use ,
* modify , copy , or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License v .2 .1 .
*
* 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 ,
2016-01-21 13:49:46 +03:00
* Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 USA
2015-08-05 12:40:00 +03:00
*/
# include "dmlib.h"
2015-08-19 22:39:10 +03:00
# include "math.h" /* log10() */
2016-08-18 15:41:46 +03:00
# include <sys/sysmacros.h>
2016-06-30 23:46:43 +03:00
# include <sys/ioctl.h>
# include <sys/vfs.h> /* fstatfs */
2016-07-09 01:38:32 +03:00
# ifdef __linux__
# include <linux/fs.h> /* FS_IOC_FIEMAP */
# endif
# ifdef HAVE_LINUX_FIEMAP_H
# include <linux/fiemap.h> /* fiemap */
# endif
# ifdef HAVE_LINUX_MAGIC_H
# include <linux/magic.h> /* BTRFS_SUPER_MAGIC */
# endif
2016-06-30 23:46:43 +03:00
2015-08-05 12:40:00 +03:00
# define DM_STATS_REGION_NOT_PRESENT UINT64_MAX
2016-06-19 15:30:46 +03:00
# define DM_STATS_GROUP_NOT_PRESENT DM_STATS_GROUP_NONE
2015-08-05 12:40:00 +03:00
2015-08-19 22:39:10 +03:00
# define NSEC_PER_USEC 1000L
2015-08-05 12:40:00 +03:00
# define NSEC_PER_MSEC 1000000L
# define NSEC_PER_SEC 1000000000L
2015-08-20 13:55:06 +03:00
# define PRECISE_ARG "precise_timestamps"
2015-08-19 22:39:10 +03:00
# define HISTOGRAM_ARG "histogram:"
2016-06-16 21:40:43 +03:00
# define STATS_ROW_BUF_LEN 4096
# define STATS_MSG_BUF_LEN 1024
2016-07-08 23:13:25 +03:00
# define STATS_FIE_BUF_LEN 2048
2016-06-16 21:40:43 +03:00
2016-07-08 23:12:18 +03:00
# define SECTOR_SHIFT 9L
2015-08-19 22:39:10 +03:00
/* Histogram bin */
struct dm_histogram_bin {
uint64_t upper ; /* Upper bound on this bin. */
uint64_t count ; /* Count value for this bin. */
} ;
struct dm_histogram {
/* The stats handle this histogram belongs to. */
const struct dm_stats * dms ;
/* The region this histogram belongs to. */
const struct dm_stats_region * region ;
uint64_t sum ; /* Sum of histogram bin counts. */
int nr_bins ; /* Number of histogram bins assigned. */
struct dm_histogram_bin bins [ 0 ] ;
} ;
2015-08-20 13:55:06 +03:00
2015-08-05 12:40:00 +03:00
/*
* See Documentation / device - mapper / statistics . txt for full descriptions
* of the device - mapper statistics counter fields .
*/
struct dm_stats_counters {
uint64_t reads ; /* Num reads completed */
uint64_t reads_merged ; /* Num reads merged */
uint64_t read_sectors ; /* Num sectors read */
uint64_t read_nsecs ; /* Num milliseconds spent reading */
uint64_t writes ; /* Num writes completed */
uint64_t writes_merged ; /* Num writes merged */
uint64_t write_sectors ; /* Num sectors written */
uint64_t write_nsecs ; /* Num milliseconds spent writing */
uint64_t io_in_progress ; /* Num I/Os currently in progress */
uint64_t io_nsecs ; /* Num milliseconds spent doing I/Os */
uint64_t weighted_io_nsecs ; /* Weighted num milliseconds doing I/Os */
uint64_t total_read_nsecs ; /* Total time spent reading in milliseconds */
uint64_t total_write_nsecs ; /* Total time spent writing in milliseconds */
2015-08-19 22:39:10 +03:00
struct dm_histogram * histogram ; /* Histogram. */
2015-08-05 12:40:00 +03:00
} ;
struct dm_stats_region {
uint64_t region_id ; /* as returned by @stats_list */
2016-02-29 20:52:29 +03:00
uint64_t group_id ;
2015-08-05 12:40:00 +03:00
uint64_t start ;
uint64_t len ;
uint64_t step ;
char * program_id ;
char * aux_data ;
uint64_t timescale ; /* precise_timestamps is per-region */
2015-08-19 22:39:10 +03:00
struct dm_histogram * bounds ; /* histogram configuration */
2016-07-03 23:59:31 +03:00
struct dm_histogram * histogram ; /* aggregate cache */
2015-08-05 12:40:00 +03:00
struct dm_stats_counters * counters ;
} ;
2016-02-29 20:52:29 +03:00
struct dm_stats_group {
uint64_t group_id ;
const char * alias ;
dm_bitset_t regions ;
2016-07-03 23:59:31 +03:00
struct dm_histogram * histogram ;
2016-02-29 20:52:29 +03:00
} ;
2015-08-05 12:40:00 +03:00
struct dm_stats {
/* device binding */
2016-03-10 19:51:02 +03:00
int bind_major ; /* device major that this dm_stats object is bound to */
int bind_minor ; /* device minor that this dm_stats object is bound to */
char * bind_name ; /* device-mapper device name */
char * bind_uuid ; /* device-mapper UUID */
2015-08-05 12:40:00 +03:00
char * program_id ; /* default program_id for this handle */
2016-06-17 14:17:33 +03:00
const char * name ; /* cached device_name used for reporting */
2015-08-05 12:40:00 +03:00
struct dm_pool * mem ; /* memory pool for region and counter tables */
2015-08-19 22:39:10 +03:00
struct dm_pool * hist_mem ; /* separate pool for histogram tables */
2016-02-29 20:52:29 +03:00
struct dm_pool * group_mem ; /* separate pool for group tables */
2015-08-05 12:40:00 +03:00
uint64_t nr_regions ; /* total number of present regions */
uint64_t max_region ; /* size of the regions table */
uint64_t interval_ns ; /* sampling interval in nanoseconds */
2015-08-20 13:55:06 +03:00
uint64_t timescale ; /* default sample value multiplier */
int precise ; /* use precise_timestamps when creating regions */
2015-08-05 12:40:00 +03:00
struct dm_stats_region * regions ;
2016-02-29 20:52:29 +03:00
struct dm_stats_group * groups ;
2015-08-05 12:40:00 +03:00
/* statistics cursor */
2016-06-19 15:30:46 +03:00
uint64_t walk_flags ; /* walk control flags */
uint64_t cur_flags ;
uint64_t cur_group ;
2015-08-05 12:40:00 +03:00
uint64_t cur_region ;
uint64_t cur_area ;
} ;
# define PROC_SELF_COMM " / proc / self / comm"
static char * _program_id_from_proc ( void )
{
FILE * comm = NULL ;
2016-06-16 21:40:43 +03:00
char buf [ STATS_ROW_BUF_LEN ] ;
2015-08-05 12:40:00 +03:00
if ( ! ( comm = fopen ( PROC_SELF_COMM , " r " ) ) )
return_NULL ;
2015-08-10 12:08:03 +03:00
if ( ! fgets ( buf , sizeof ( buf ) , comm ) ) {
log_error ( " Could not read from %s " , PROC_SELF_COMM ) ;
2015-09-06 01:56:30 +03:00
if ( fclose ( comm ) )
2015-08-10 21:23:41 +03:00
stack ;
2015-08-10 12:08:03 +03:00
return NULL ;
}
2015-08-05 12:40:00 +03:00
2015-08-10 21:23:41 +03:00
if ( fclose ( comm ) )
stack ;
2015-08-05 12:40:00 +03:00
return dm_strdup ( buf ) ;
}
2015-08-19 22:39:10 +03:00
static uint64_t _nr_areas ( uint64_t len , uint64_t step )
{
/* Default is one area. */
if ( ! len | | ! step )
return 1 ;
/*
* drivers / md / dm - stats . c : : message_stats_create ( )
* A region may be sub - divided into areas with their own counters .
* Any partial area at the end of the region is treated as an
* additional complete area .
*/
return ( len + step - 1 ) / step ;
}
static uint64_t _nr_areas_region ( struct dm_stats_region * region )
{
return _nr_areas ( region - > len , region - > step ) ;
}
2015-08-05 12:40:00 +03:00
struct dm_stats * dm_stats_create ( const char * program_id )
{
2015-08-19 22:39:10 +03:00
size_t hist_hint = sizeof ( struct dm_histogram_bin ) ;
2016-02-29 20:52:29 +03:00
size_t group_hint = sizeof ( struct dm_stats_group ) ;
2015-08-05 12:40:00 +03:00
struct dm_stats * dms = NULL ;
2015-08-10 12:15:22 +03:00
if ( ! ( dms = dm_zalloc ( sizeof ( * dms ) ) ) )
2015-08-05 12:40:00 +03:00
return_NULL ;
2015-08-19 22:39:10 +03:00
/* FIXME: better hint. */
2015-09-07 14:08:34 +03:00
if ( ! ( dms - > mem = dm_pool_create ( " stats_pool " , 4096 ) ) ) {
dm_free ( dms ) ;
return_NULL ;
}
2015-08-05 12:40:00 +03:00
2015-08-19 22:39:10 +03:00
if ( ! ( dms - > hist_mem = dm_pool_create ( " histogram_pool " , hist_hint ) ) )
2015-09-07 14:08:34 +03:00
goto_bad ;
2015-08-19 22:39:10 +03:00
2016-02-29 20:52:29 +03:00
if ( ! ( dms - > group_mem = dm_pool_create ( " group_pool " , group_hint ) ) )
goto_bad ;
2015-08-05 12:40:00 +03:00
if ( ! program_id | | ! strlen ( program_id ) )
dms - > program_id = _program_id_from_proc ( ) ;
else
dms - > program_id = dm_strdup ( program_id ) ;
2015-09-07 14:08:34 +03:00
if ( ! dms - > program_id ) {
2016-07-04 20:20:09 +03:00
log_error ( " Could not allocate memory for program_id " ) ;
goto bad ;
2015-09-07 14:08:34 +03:00
}
2016-03-10 19:51:02 +03:00
dms - > bind_major = - 1 ;
dms - > bind_minor = - 1 ;
dms - > bind_name = NULL ;
dms - > bind_uuid = NULL ;
2015-08-05 12:40:00 +03:00
2016-06-17 14:17:33 +03:00
dms - > name = NULL ;
2015-08-20 13:55:06 +03:00
/* by default all regions use msec precision */
2015-08-05 12:40:00 +03:00
dms - > timescale = NSEC_PER_MSEC ;
2015-08-20 13:55:06 +03:00
dms - > precise = 0 ;
2015-08-05 12:40:00 +03:00
dms - > nr_regions = DM_STATS_REGION_NOT_PRESENT ;
dms - > max_region = DM_STATS_REGION_NOT_PRESENT ;
dms - > regions = NULL ;
2016-06-19 15:30:46 +03:00
/* maintain compatibility with earlier walk version */
dms - > walk_flags = dms - > cur_flags = DM_STATS_WALK_DEFAULT ;
2015-08-05 12:40:00 +03:00
return dms ;
2015-09-06 01:56:30 +03:00
bad :
2015-09-07 14:08:34 +03:00
dm_pool_destroy ( dms - > mem ) ;
2016-02-29 20:52:29 +03:00
if ( dms - > hist_mem )
dm_pool_destroy ( dms - > hist_mem ) ;
if ( dms - > group_mem )
dm_pool_destroy ( dms - > group_mem ) ;
2015-08-10 12:15:22 +03:00
dm_free ( dms ) ;
return NULL ;
2015-08-05 12:40:00 +03:00
}
2016-02-29 20:52:29 +03:00
/*
2015-08-05 12:40:00 +03:00
* Test whether the stats region pointed to by region is present .
*/
static int _stats_region_present ( const struct dm_stats_region * region )
{
return ! ( region - > region_id = = DM_STATS_REGION_NOT_PRESENT ) ;
}
2016-02-29 20:52:29 +03:00
/*
* Test whether the stats group pointed to by group is present .
*/
static int _stats_group_present ( const struct dm_stats_group * group )
{
return ! ( group - > group_id = = DM_STATS_GROUP_NOT_PRESENT ) ;
}
/*
* Test whether a stats group id is present .
*/
static int _stats_group_id_present ( const struct dm_stats * dms , uint64_t id )
{
struct dm_stats_group * group = NULL ;
if ( ! dms | | ! dms - > regions )
return_0 ;
if ( id > dms - > max_region )
return 0 ;
group = & dms - > groups [ id ] ;
return _stats_group_present ( group ) ;
}
/*
* Test whether the given region_id is a member of any group .
*/
static uint64_t _stats_region_is_grouped ( const struct dm_stats * dms ,
uint64_t region_id )
{
uint64_t group_id ;
if ( region_id = = DM_STATS_GROUP_NOT_PRESENT )
return 0 ;
if ( ! _stats_region_present ( & dms - > regions [ region_id ] ) )
return 0 ;
group_id = dms - > regions [ region_id ] . group_id ;
return group_id ! = DM_STATS_GROUP_NOT_PRESENT ;
}
2015-08-19 22:39:10 +03:00
static void _stats_histograms_destroy ( struct dm_pool * mem ,
struct dm_stats_region * region )
{
/* Unpopulated handle. */
if ( ! region - > counters )
return ;
2015-09-07 19:53:56 +03:00
/*
2015-09-15 15:17:50 +03:00
* Free everything in the pool back to the first histogram .
2015-09-07 19:53:56 +03:00
*/
if ( region - > counters [ 0 ] . histogram )
dm_pool_free ( mem , region - > counters [ 0 ] . histogram ) ;
2015-08-19 22:39:10 +03:00
}
2015-08-05 12:40:00 +03:00
static void _stats_region_destroy ( struct dm_stats_region * region )
{
if ( ! _stats_region_present ( region ) )
return ;
2016-02-29 20:52:29 +03:00
region - > start = region - > len = region - > step = 0 ;
region - > timescale = 0 ;
/*
* Don ' t free counters and histogram bounds here : they are
* dropped from the pool along with the corresponding
* regions table .
2015-08-19 22:39:10 +03:00
*
* The following objects are all allocated with dm_malloc .
2015-08-05 12:40:00 +03:00
*/
2016-02-29 20:52:29 +03:00
region - > counters = NULL ;
region - > bounds = NULL ;
2015-08-05 12:40:00 +03:00
if ( region - > program_id )
dm_free ( region - > program_id ) ;
2016-02-29 20:52:29 +03:00
region - > program_id = NULL ;
2015-08-05 12:40:00 +03:00
if ( region - > aux_data )
dm_free ( region - > aux_data ) ;
2016-02-29 20:52:29 +03:00
region - > aux_data = NULL ;
region - > region_id = DM_STATS_REGION_NOT_PRESENT ;
2015-08-05 12:40:00 +03:00
}
static void _stats_regions_destroy ( struct dm_stats * dms )
{
struct dm_pool * mem = dms - > mem ;
uint64_t i ;
if ( ! dms - > regions )
return ;
/* walk backwards to obey pool order */
2015-08-19 22:39:10 +03:00
for ( i = dms - > max_region ; ( i ! = DM_STATS_REGION_NOT_PRESENT ) ; i - - ) {
_stats_histograms_destroy ( dms - > hist_mem , & dms - > regions [ i ] ) ;
2015-08-05 12:40:00 +03:00
_stats_region_destroy ( & dms - > regions [ i ] ) ;
2015-08-19 22:39:10 +03:00
}
2015-08-05 12:40:00 +03:00
dm_pool_free ( mem , dms - > regions ) ;
2016-12-09 18:47:55 +03:00
dms - > regions = NULL ;
2015-08-05 12:40:00 +03:00
}
2016-02-29 20:52:29 +03:00
static void _stats_group_destroy ( struct dm_stats_group * group )
{
if ( ! _stats_group_present ( group ) )
return ;
2016-07-03 23:59:31 +03:00
group - > histogram = NULL ;
2016-02-29 20:52:29 +03:00
if ( group - > alias ) {
dm_free ( ( char * ) group - > alias ) ;
group - > alias = NULL ;
}
if ( group - > regions ) {
dm_bitset_destroy ( group - > regions ) ;
group - > regions = NULL ;
}
group - > group_id = DM_STATS_GROUP_NOT_PRESENT ;
}
static void _stats_groups_destroy ( struct dm_stats * dms )
{
uint64_t i ;
if ( ! dms - > groups )
return ;
for ( i = dms - > max_region ; ( i ! = DM_STATS_REGION_NOT_PRESENT ) ; i - - )
_stats_group_destroy ( & dms - > groups [ i ] ) ;
dm_pool_free ( dms - > group_mem , dms - > groups ) ;
2016-12-09 18:49:06 +03:00
dms - > groups = NULL ;
2016-02-29 20:52:29 +03:00
}
2015-08-05 12:40:00 +03:00
static int _set_stats_device ( struct dm_stats * dms , struct dm_task * dmt )
{
2016-03-10 19:51:02 +03:00
if ( dms - > bind_name )
return dm_task_set_name ( dmt , dms - > bind_name ) ;
if ( dms - > bind_uuid )
return dm_task_set_uuid ( dmt , dms - > bind_uuid ) ;
if ( dms - > bind_major > 0 )
return dm_task_set_major ( dmt , dms - > bind_major )
& & dm_task_set_minor ( dmt , dms - > bind_minor ) ;
2015-08-05 12:40:00 +03:00
return_0 ;
}
2016-02-29 20:52:29 +03:00
static int _stats_bound ( const struct dm_stats * dms )
2015-08-05 12:40:00 +03:00
{
2016-03-10 19:51:02 +03:00
if ( dms - > bind_major > 0 | | dms - > bind_name | | dms - > bind_uuid )
2015-08-05 12:40:00 +03:00
return 1 ;
/* %p format specifier expects a void pointer. */
log_debug ( " Stats handle at %p is not bound. " , ( void * ) dms ) ;
return 0 ;
}
static void _stats_clear_binding ( struct dm_stats * dms )
{
2016-03-10 19:51:02 +03:00
if ( dms - > bind_name )
dm_pool_free ( dms - > mem , dms - > bind_name ) ;
if ( dms - > bind_uuid )
dm_pool_free ( dms - > mem , dms - > bind_uuid ) ;
2016-06-17 14:17:33 +03:00
if ( dms - > name )
dm_free ( ( char * ) dms - > name ) ;
2015-08-05 12:40:00 +03:00
2016-03-10 19:51:02 +03:00
dms - > bind_name = dms - > bind_uuid = NULL ;
dms - > bind_major = dms - > bind_minor = - 1 ;
2016-06-17 14:17:33 +03:00
dms - > name = NULL ;
2015-08-05 12:40:00 +03:00
}
int dm_stats_bind_devno ( struct dm_stats * dms , int major , int minor )
{
_stats_clear_binding ( dms ) ;
_stats_regions_destroy ( dms ) ;
2016-02-29 20:52:29 +03:00
_stats_groups_destroy ( dms ) ;
2015-08-05 12:40:00 +03:00
2016-03-10 19:51:02 +03:00
dms - > bind_major = major ;
dms - > bind_minor = minor ;
2015-08-05 12:40:00 +03:00
return 1 ;
}
int dm_stats_bind_name ( struct dm_stats * dms , const char * name )
{
_stats_clear_binding ( dms ) ;
_stats_regions_destroy ( dms ) ;
2016-02-29 20:52:29 +03:00
_stats_groups_destroy ( dms ) ;
2015-08-05 12:40:00 +03:00
2016-03-10 19:51:02 +03:00
if ( ! ( dms - > bind_name = dm_pool_strdup ( dms - > mem , name ) ) )
2015-08-05 12:40:00 +03:00
return_0 ;
return 1 ;
}
int dm_stats_bind_uuid ( struct dm_stats * dms , const char * uuid )
{
_stats_clear_binding ( dms ) ;
_stats_regions_destroy ( dms ) ;
2016-02-29 20:52:29 +03:00
_stats_groups_destroy ( dms ) ;
2015-08-05 12:40:00 +03:00
2016-03-10 19:51:02 +03:00
if ( ! ( dms - > bind_uuid = dm_pool_strdup ( dms - > mem , uuid ) ) )
2015-08-05 12:40:00 +03:00
return_0 ;
return 1 ;
}
2015-08-20 13:55:06 +03:00
static int _stats_check_precise_timestamps ( const struct dm_stats * dms )
{
/* Already checked? */
if ( dms & & dms - > precise )
return 1 ;
2015-08-19 22:39:10 +03:00
2015-08-20 13:55:06 +03:00
return dm_message_supports_precise_timestamps ( ) ;
}
int dm_stats_driver_supports_precise ( void )
{
return _stats_check_precise_timestamps ( NULL ) ;
}
2015-08-19 22:39:10 +03:00
int dm_stats_driver_supports_histogram ( void )
{
return _stats_check_precise_timestamps ( NULL ) ;
}
2015-09-07 20:27:22 +03:00
static int _fill_hist_arg ( char * hist_arg , size_t hist_len , uint64_t scale ,
struct dm_histogram * bounds )
{
int i , l , len = 0 , nr_bins ;
char * arg = hist_arg ;
uint64_t value ;
nr_bins = bounds - > nr_bins ;
for ( i = 0 ; i < nr_bins ; i + + ) {
value = bounds - > bins [ i ] . upper / scale ;
if ( ( l = dm_snprintf ( arg , hist_len - len , FMTu64 " %s " , value ,
( i = = ( nr_bins - 1 ) ) ? " " : " , " ) ) < 0 )
return_0 ;
len + = l ;
arg + = l ;
}
return 1 ;
}
static void * _get_hist_arg ( struct dm_histogram * bounds , uint64_t scale ,
size_t * len )
{
struct dm_histogram_bin * entry , * bins ;
size_t hist_len = 1 ; /* terminating '\0' */
double value ;
entry = bins = bounds - > bins ;
entry + = bounds - > nr_bins - 1 ;
while ( entry > = bins ) {
value = ( double ) ( entry - - ) - > upper ;
/* Use lround to avoid size_t -> double cast warning. */
hist_len + = 1 + ( size_t ) lround ( log10 ( value / scale ) ) ;
if ( entry ! = bins )
hist_len + + ; /* ',' */
}
* len = hist_len ;
return dm_zalloc ( hist_len ) ;
}
2015-08-19 22:39:10 +03:00
static char * _build_histogram_arg ( struct dm_histogram * bounds , int * precise )
{
struct dm_histogram_bin * entry , * bins ;
2015-09-07 20:27:22 +03:00
size_t hist_len ;
char * hist_arg ;
2015-08-19 22:39:10 +03:00
uint64_t scale ;
entry = bins = bounds - > bins ;
/* Empty histogram is invalid. */
if ( ! bounds - > nr_bins ) {
log_error ( " Cannot format empty histogram description. " ) ;
return NULL ;
}
2015-09-07 20:27:22 +03:00
/* Validate entries and set *precise if precision < 1ms. */
2015-08-19 22:39:10 +03:00
entry + = bounds - > nr_bins - 1 ;
2015-09-07 20:27:22 +03:00
while ( entry > = bins ) {
2015-08-19 22:39:10 +03:00
if ( entry ! = bins ) {
if ( entry - > upper < ( entry - 1 ) - > upper ) {
log_error ( " Histogram boundaries must be in "
" order of increasing magnitude. " ) ;
return 0 ;
}
}
/*
* Only enable precise_timestamps automatically if any
* value in the histogram bounds uses precision < 1 ms .
*/
2015-09-07 20:27:22 +03:00
if ( ( ( entry - - ) - > upper % NSEC_PER_MSEC ) & & ! * precise )
2015-08-19 22:39:10 +03:00
* precise = 1 ;
}
2015-09-07 20:27:22 +03:00
scale = ( * precise ) ? 1 : NSEC_PER_MSEC ;
/* Calculate hist_len and allocate a character buffer. */
if ( ! ( hist_arg = _get_hist_arg ( bounds , scale , & hist_len ) ) ) {
2015-08-19 22:39:10 +03:00
log_error ( " Could not allocate memory for histogram argument. " ) ;
return 0 ;
}
2015-09-07 20:27:22 +03:00
/* Fill hist_arg with boundary strings. */
if ( ! _fill_hist_arg ( hist_arg , hist_len , scale , bounds ) )
goto_bad ;
2015-08-19 22:39:10 +03:00
return hist_arg ;
2015-09-06 01:56:30 +03:00
bad :
2015-08-19 22:39:10 +03:00
log_error ( " Could not build histogram arguments. " ) ;
dm_free ( hist_arg ) ;
2015-09-06 01:56:30 +03:00
2015-08-19 22:39:10 +03:00
return NULL ;
}
2015-08-05 12:40:00 +03:00
static struct dm_task * _stats_send_message ( struct dm_stats * dms , char * msg )
{
struct dm_task * dmt ;
if ( ! ( dmt = dm_task_create ( DM_DEVICE_TARGET_MSG ) ) )
return_0 ;
if ( ! _set_stats_device ( dms , dmt ) )
2015-09-06 01:56:30 +03:00
goto_bad ;
2015-08-05 12:40:00 +03:00
if ( ! dm_task_set_message ( dmt , msg ) )
2015-09-06 01:56:30 +03:00
goto_bad ;
2015-08-05 12:40:00 +03:00
if ( ! dm_task_run ( dmt ) )
2015-09-06 01:56:30 +03:00
goto_bad ;
2015-08-05 12:40:00 +03:00
return dmt ;
2015-09-06 01:56:30 +03:00
bad :
2015-08-05 12:40:00 +03:00
dm_task_destroy ( dmt ) ;
return NULL ;
}
2016-06-17 14:17:33 +03:00
/*
* Cache the dm device_name for the device bound to dms .
*/
static int _stats_set_name_cache ( struct dm_stats * dms )
{
struct dm_task * dmt ;
if ( dms - > name )
return 1 ;
if ( ! ( dmt = dm_task_create ( DM_DEVICE_INFO ) ) )
return_0 ;
if ( ! _set_stats_device ( dms , dmt ) )
goto_bad ;
if ( ! dm_task_run ( dmt ) )
goto_bad ;
if ( ! ( dms - > name = dm_strdup ( dm_task_get_name ( dmt ) ) ) )
goto_bad ;
dm_task_destroy ( dmt ) ;
return 1 ;
bad :
log_error ( " Could not retrieve device-mapper name for device. " ) ;
dm_task_destroy ( dmt ) ;
return 0 ;
}
2016-02-29 20:52:29 +03:00
/*
* update region group_id values
*/
static void _stats_update_groups ( struct dm_stats * dms )
{
struct dm_stats_group * group ;
uint64_t group_id , i ;
for ( group_id = 0 ; group_id < dms - > max_region + 1 ; group_id + + ) {
if ( ! _stats_group_id_present ( dms , group_id ) )
continue ;
group = & dms - > groups [ group_id ] ;
for ( i = dm_bit_get_first ( group - > regions ) ;
i ! = DM_STATS_GROUP_NOT_PRESENT ;
i = dm_bit_get_next ( group - > regions , i ) )
dms - > regions [ i ] . group_id = group_id ;
}
}
2016-12-13 17:31:39 +03:00
static void _check_group_regions_present ( struct dm_stats * dms ,
struct dm_stats_group * group )
{
dm_bitset_t regions = group - > regions ;
int64_t i , group_id ;
group_id = i = dm_bit_get_first ( regions ) ;
for ( ; i > 0 ; dm_bit_get_next ( regions , i ) )
if ( ! _stats_region_present ( & dms - > regions [ i ] ) ) {
log_warn ( " Group descriptor " FMTi64 " contains "
" non-existent region_id " FMTi64 " . " ,
group_id , i ) ;
dm_bit_clear ( regions , i ) ;
}
}
2016-02-29 20:52:29 +03:00
/*
* Parse a DMS_GROUP group descriptor embedded in a region ' s aux_data .
*
* DMS_GROUP = " ALIAS:MEMBERS "
*
* ALIAS : group alias
* MEMBERS : list of group member region ids .
*
*/
# define DMS_GROUP_TAG "DMS_GROUP="
# define DMS_GROUP_TAG_LEN (sizeof(DMS_GROUP_TAG) - 1)
# define DMS_GROUP_SEP ':'
# define DMS_AUX_SEP "#"
2016-12-12 01:41:45 +03:00
2016-02-29 20:52:29 +03:00
static int _parse_aux_data_group ( struct dm_stats * dms ,
struct dm_stats_region * region ,
struct dm_stats_group * group )
{
char * alias , * c , * end ;
dm_bitset_t regions ;
memset ( group , 0 , sizeof ( * group ) ) ;
group - > group_id = DM_STATS_GROUP_NOT_PRESENT ;
/* find start of group tag */
c = strstr ( region - > aux_data , DMS_GROUP_TAG ) ;
if ( ! c )
return 1 ; /* no group is not an error */
alias = c + strlen ( DMS_GROUP_TAG ) ;
c = strchr ( c , DMS_GROUP_SEP ) ;
if ( ! c ) {
log_error ( " Found malformed group tag while reading aux_data " ) ;
return 0 ;
}
/* terminate alias and advance to members */
* ( c + + ) = ' \0 ' ;
log_debug ( " Read alias '%s' from aux_data " , alias ) ;
if ( ! c ) {
log_error ( " Found malformed group descriptor while "
" reading aux_data, expected '%c' " , DMS_GROUP_SEP ) ;
return 0 ;
}
/* if user aux_data follows make sure we have a terminated
* string to pass to dm_bitset_parse_list ( ) .
*/
end = strstr ( c , DMS_AUX_SEP ) ;
if ( ! end )
end = c + strlen ( c ) ;
* ( end + + ) = ' \0 ' ;
2016-12-12 01:41:45 +03:00
if ( ! ( regions = dm_bitset_parse_list ( c , NULL , 0 ) ) ) {
2016-02-29 20:52:29 +03:00
log_error ( " Could not parse member list while "
" reading group aux_data " ) ;
return 0 ;
}
group - > group_id = dm_bit_get_first ( regions ) ;
2016-12-13 16:56:10 +03:00
if ( group - > group_id ! = region - > region_id ) {
log_error ( " Found invalid group descriptor in region " FMTu64
" aux_data. " , region - > region_id ) ;
group - > group_id = DM_STATS_GROUP_NOT_PRESENT ;
goto bad ;
}
2016-02-29 20:52:29 +03:00
2016-12-13 16:56:10 +03:00
group - > regions = regions ;
2016-02-29 20:52:29 +03:00
group - > alias = NULL ;
if ( strlen ( alias ) ) {
group - > alias = dm_strdup ( alias ) ;
if ( ! group - > alias ) {
log_error ( " Could not allocate memory for group alias " ) ;
goto bad ;
}
}
/* separate group tag from user aux_data */
2016-07-06 18:29:06 +03:00
if ( ( strlen ( end ) > 1 ) | | strncmp ( end , " - " , 1 ) )
2016-02-29 20:52:29 +03:00
c = dm_strdup ( end ) ;
else
c = dm_strdup ( " " ) ;
if ( ! c ) {
log_error ( " Could not allocate memory for user aux_data " ) ;
goto bad_alias ;
}
dm_free ( region - > aux_data ) ;
region - > aux_data = c ;
log_debug ( " Found group_id " FMTu64 " : alias= \" %s \" " , group - > group_id ,
( group - > alias ) ? group - > alias : " " ) ;
return 1 ;
bad_alias :
dm_free ( ( char * ) group - > alias ) ;
bad :
dm_bitset_destroy ( regions ) ;
return 0 ;
}
2015-08-19 22:39:10 +03:00
/*
* Parse a histogram specification returned by the kernel in a
* @ stats_list response .
*/
static int _stats_parse_histogram_spec ( struct dm_stats * dms ,
struct dm_stats_region * region ,
const char * histogram )
{
2016-02-22 16:11:02 +03:00
static const char _valid_chars [ ] = " 0123456789, " ;
2015-09-07 22:08:10 +03:00
uint64_t scale = region - > timescale , this_val = 0 ;
2015-08-19 22:39:10 +03:00
struct dm_pool * mem = dms - > hist_mem ;
struct dm_histogram_bin cur ;
struct dm_histogram hist ;
2015-09-04 13:40:19 +03:00
int nr_bins = 1 ;
2015-09-07 22:08:10 +03:00
const char * c , * v , * val_start ;
char * p , * endptr = NULL ;
2015-08-19 22:39:10 +03:00
/* Advance past "histogram:". */
histogram = strchr ( histogram , ' : ' ) ;
2015-09-06 01:56:30 +03:00
if ( ! histogram ) {
2015-08-19 22:39:10 +03:00
log_error ( " Could not parse histogram description. " ) ;
return 0 ;
}
histogram + + ;
/* @stats_list rows are newline terminated. */
if ( ( p = strchr ( histogram , ' \n ' ) ) )
* p = ' \0 ' ;
if ( ! dm_pool_begin_object ( mem , sizeof ( cur ) ) )
return_0 ;
memset ( & hist , 0 , sizeof ( hist ) ) ;
hist . nr_bins = 0 ; /* fix later */
hist . region = region ;
hist . dms = dms ;
2015-09-06 01:56:30 +03:00
if ( ! dm_pool_grow_object ( mem , & hist , sizeof ( hist ) ) )
goto_bad ;
2015-08-19 22:39:10 +03:00
c = histogram ;
do {
2015-09-06 01:56:30 +03:00
for ( v = _valid_chars ; * v ; v + + )
2015-08-19 22:39:10 +03:00
if ( * c = = * v )
break ;
2015-09-06 01:56:30 +03:00
if ( ! * v ) {
stack ;
2015-08-19 22:39:10 +03:00
goto badchar ;
2015-09-06 01:56:30 +03:00
}
2015-08-19 22:39:10 +03:00
if ( * c = = ' , ' ) {
log_error ( " Invalid histogram description: %s " ,
histogram ) ;
2015-09-06 01:56:30 +03:00
goto bad ;
2015-08-19 22:39:10 +03:00
} else {
2015-09-07 22:08:10 +03:00
val_start = c ;
endptr = NULL ;
2015-08-19 22:39:10 +03:00
this_val = strtoull ( val_start , & endptr , 10 ) ;
if ( ! endptr ) {
log_error ( " Could not parse histogram boundary. " ) ;
2015-09-06 01:56:30 +03:00
goto bad ;
2015-08-19 22:39:10 +03:00
}
c = endptr ; /* Advance to units, comma, or end. */
if ( * c = = ' , ' )
c + + ;
2015-09-06 01:56:30 +03:00
else if ( * c | | ( * c = = ' ' ) ) { /* Expected ',' or NULL. */
stack ;
2015-08-19 22:39:10 +03:00
goto badchar ;
2015-09-06 01:56:30 +03:00
}
2015-08-19 22:39:10 +03:00
if ( * c = = ' , ' )
c + + ;
cur . upper = scale * this_val ;
cur . count = 0 ;
if ( ! dm_pool_grow_object ( mem , & cur , sizeof ( cur ) ) )
2015-09-06 01:56:30 +03:00
goto_bad ;
2015-08-19 22:39:10 +03:00
nr_bins + + ;
}
} while ( * c & & ( * c ! = ' ' ) ) ;
/* final upper bound. */
cur . upper = UINT64_MAX ;
if ( ! dm_pool_grow_object ( mem , & cur , sizeof ( cur ) ) )
2015-09-06 01:56:30 +03:00
goto_bad ;
2015-08-19 22:39:10 +03:00
region - > bounds = dm_pool_end_object ( mem ) ;
if ( ! region - > bounds )
return_0 ;
region - > bounds - > nr_bins = nr_bins ;
log_debug ( " Added region histogram spec with %d entries. " , nr_bins ) ;
return 1 ;
badchar :
log_error ( " Invalid character in histogram: '%c' (0x%x) " , * c , * c ) ;
2015-09-06 01:56:30 +03:00
bad :
2015-08-19 22:39:10 +03:00
dm_pool_abandon_object ( mem ) ;
return 0 ;
}
static int _stats_parse_list_region ( struct dm_stats * dms ,
struct dm_stats_region * region , char * line )
2015-08-05 12:40:00 +03:00
{
2016-06-16 21:40:43 +03:00
char * p = NULL , string_data [ STATS_ROW_BUF_LEN ] ;
2016-02-29 20:52:29 +03:00
char * program_id , * aux_data , * stats_args ;
char * empty_string = ( char * ) " " ;
2015-08-05 12:40:00 +03:00
int r ;
2015-08-20 13:55:06 +03:00
memset ( string_data , 0 , sizeof ( string_data ) ) ;
/*
* Parse fixed fields , line format :
*
* < region_id > : < start_sector > + < length > < step > < string data >
*
* Maximum string data size is 4096 - 1 bytes .
2015-08-05 12:40:00 +03:00
*/
2015-08-20 13:55:06 +03:00
r = sscanf ( line , FMTu64 " : " FMTu64 " + " FMTu64 " " FMTu64 " %4095c " ,
& region - > region_id , & region - > start , & region - > len ,
& region - > step , string_data ) ;
2015-08-05 12:40:00 +03:00
2015-08-20 13:55:06 +03:00
if ( r ! = 5 )
2015-08-05 12:40:00 +03:00
return_0 ;
2015-08-20 13:55:06 +03:00
/* program_id is guaranteed to be first. */
program_id = string_data ;
/*
* FIXME : support embedded ' \ ' in string data :
* s / strchr / _find_unescaped_space ( ) /
*/
if ( ( p = strchr ( string_data , ' ' ) ) ) {
/* terminate program_id string. */
* p = ' \0 ' ;
2016-02-29 20:52:29 +03:00
if ( ! strncmp ( program_id , " - " , 1 ) )
2015-08-20 13:55:06 +03:00
program_id = empty_string ;
aux_data = p + 1 ;
if ( ( p = strchr ( aux_data , ' ' ) ) ) {
/* terminate aux_data string. */
* p = ' \0 ' ;
stats_args = p + 1 ;
} else
stats_args = empty_string ;
2016-02-29 20:52:29 +03:00
/* no aux_data? */
if ( ! strncmp ( aux_data , " - " , 1 ) )
aux_data = empty_string ;
else
/* remove trailing newline */
aux_data [ strlen ( aux_data ) - 1 ] = ' \0 ' ;
2015-08-20 13:55:06 +03:00
} else
aux_data = stats_args = empty_string ;
if ( strstr ( stats_args , PRECISE_ARG ) )
region - > timescale = 1 ;
else
region - > timescale = NSEC_PER_MSEC ;
2015-08-05 12:40:00 +03:00
2015-08-19 22:39:10 +03:00
if ( ( p = strstr ( stats_args , HISTOGRAM_ARG ) ) ) {
if ( ! _stats_parse_histogram_spec ( dms , region , p ) )
return_0 ;
} else
region - > bounds = NULL ;
2016-07-03 23:59:31 +03:00
/* clear aggregate cache */
region - > histogram = NULL ;
2016-02-29 20:52:29 +03:00
region - > group_id = DM_STATS_GROUP_NOT_PRESENT ;
2015-08-05 12:40:00 +03:00
if ( ! ( region - > program_id = dm_strdup ( program_id ) ) )
return_0 ;
if ( ! ( region - > aux_data = dm_strdup ( aux_data ) ) ) {
dm_free ( region - > program_id ) ;
return_0 ;
}
region - > counters = NULL ;
return 1 ;
}
static int _stats_parse_list ( struct dm_stats * dms , const char * resp )
{
uint64_t max_region = 0 , nr_regions = 0 ;
2015-09-07 22:08:10 +03:00
struct dm_stats_region cur , fill ;
2016-02-29 20:52:29 +03:00
struct dm_stats_group cur_group ;
struct dm_pool * mem = dms - > mem , * group_mem = dms - > group_mem ;
2016-06-16 21:40:43 +03:00
char line [ STATS_ROW_BUF_LEN ] ;
2015-08-05 12:40:00 +03:00
FILE * list_rows ;
if ( ! resp ) {
log_error ( " Could not parse NULL @stats_list response. " ) ;
return 0 ;
}
2016-02-29 20:52:29 +03:00
_stats_regions_destroy ( dms ) ;
_stats_groups_destroy ( dms ) ;
2015-08-05 12:40:00 +03:00
/* no regions */
if ( ! strlen ( resp ) ) {
dms - > nr_regions = dms - > max_region = 0 ;
dms - > regions = NULL ;
return 1 ;
}
/*
* dm_task_get_message_response ( ) returns a ' const char * ' but
* since fmemopen also permits " w " it expects a ' char * ' .
*/
if ( ! ( list_rows = fmemopen ( ( char * ) resp , strlen ( resp ) , " r " ) ) )
return_0 ;
2016-02-29 20:52:29 +03:00
/* begin region table */
2015-08-05 12:40:00 +03:00
if ( ! dm_pool_begin_object ( mem , 1024 ) )
2015-09-06 01:56:30 +03:00
goto_bad ;
2015-08-05 12:40:00 +03:00
2016-02-29 20:52:29 +03:00
/* begin group table */
if ( ! dm_pool_begin_object ( group_mem , 32 ) )
goto_bad ;
2015-08-05 12:40:00 +03:00
while ( fgets ( line , sizeof ( line ) , list_rows ) ) {
2016-02-29 20:52:29 +03:00
cur_group . group_id = DM_STATS_GROUP_NOT_PRESENT ;
cur_group . regions = NULL ;
cur_group . alias = NULL ;
2015-08-19 22:39:10 +03:00
if ( ! _stats_parse_list_region ( dms , & cur , line ) )
2015-09-06 01:56:30 +03:00
goto_bad ;
2015-08-05 12:40:00 +03:00
/* handle holes in the list of region_ids */
if ( cur . region_id > max_region ) {
memset ( & fill , 0 , sizeof ( fill ) ) ;
2016-02-29 20:52:29 +03:00
memset ( & cur_group , 0 , sizeof ( cur_group ) ) ;
2015-08-05 12:40:00 +03:00
fill . region_id = DM_STATS_REGION_NOT_PRESENT ;
2016-02-29 20:52:29 +03:00
cur_group . group_id = DM_STATS_GROUP_NOT_PRESENT ;
2015-08-05 12:40:00 +03:00
do {
if ( ! dm_pool_grow_object ( mem , & fill , sizeof ( fill ) ) )
2015-09-06 01:56:30 +03:00
goto_bad ;
2016-02-29 20:52:29 +03:00
if ( ! dm_pool_grow_object ( group_mem , & cur_group ,
sizeof ( cur_group ) ) )
goto_bad ;
2015-08-05 12:40:00 +03:00
} while ( max_region + + < ( cur . region_id - 1 ) ) ;
}
2016-02-29 20:52:29 +03:00
if ( cur . aux_data )
if ( ! _parse_aux_data_group ( dms , & cur , & cur_group ) )
log_error ( " Failed to parse group descriptor "
" from region_id " FMTu64 " aux_data: "
" '%s' " , cur . region_id , cur . aux_data ) ;
/* continue */
2015-08-05 12:40:00 +03:00
if ( ! dm_pool_grow_object ( mem , & cur , sizeof ( cur ) ) )
2015-09-06 01:56:30 +03:00
goto_bad ;
2015-08-05 12:40:00 +03:00
2016-02-29 20:52:29 +03:00
if ( ! dm_pool_grow_object ( group_mem , & cur_group ,
sizeof ( cur_group ) ) )
goto_bad ;
2015-08-05 12:40:00 +03:00
max_region + + ;
nr_regions + + ;
}
2016-04-22 12:35:07 +03:00
if ( ! nr_regions )
/* no region data read from @stats_list */
goto bad ;
2015-08-05 12:40:00 +03:00
dms - > nr_regions = nr_regions ;
dms - > max_region = max_region - 1 ;
dms - > regions = dm_pool_end_object ( mem ) ;
2016-02-29 20:52:29 +03:00
dms - > groups = dm_pool_end_object ( group_mem ) ;
2016-12-13 17:31:39 +03:00
dm_stats_foreach_group ( dms )
_check_group_regions_present ( dms , & dms - > groups [ dms - > cur_group ] ) ;
2016-02-29 20:52:29 +03:00
_stats_update_groups ( dms ) ;
2015-08-05 12:40:00 +03:00
2015-08-10 21:23:41 +03:00
if ( fclose ( list_rows ) )
stack ;
2015-08-05 12:40:00 +03:00
return 1 ;
2015-09-06 01:56:30 +03:00
bad :
if ( fclose ( list_rows ) )
2015-08-10 21:23:41 +03:00
stack ;
2015-08-05 12:40:00 +03:00
dm_pool_abandon_object ( mem ) ;
2016-02-29 20:52:29 +03:00
dm_pool_abandon_object ( group_mem ) ;
2015-09-06 01:56:30 +03:00
2015-08-05 12:40:00 +03:00
return 0 ;
}
int dm_stats_list ( struct dm_stats * dms , const char * program_id )
{
2016-06-16 21:40:43 +03:00
char msg [ STATS_MSG_BUF_LEN ] ;
2015-08-05 12:40:00 +03:00
struct dm_task * dmt ;
int r ;
if ( ! _stats_bound ( dms ) )
return_0 ;
/* allow zero-length program_id for list */
if ( ! program_id )
program_id = dms - > program_id ;
2016-06-17 14:17:33 +03:00
if ( ! _stats_set_name_cache ( dms ) )
return_0 ;
2016-12-15 22:03:42 +03:00
if ( dms - > regions )
_stats_regions_destroy ( dms ) ;
2015-08-05 12:40:00 +03:00
r = dm_snprintf ( msg , sizeof ( msg ) , " @stats_list %s " , program_id ) ;
if ( r < 0 ) {
log_error ( " Failed to prepare stats message. " ) ;
return 0 ;
}
if ( ! ( dmt = _stats_send_message ( dms , msg ) ) )
2015-09-06 01:56:30 +03:00
return_0 ;
2015-08-05 12:40:00 +03:00
if ( ! _stats_parse_list ( dms , dm_task_get_message_response ( dmt ) ) ) {
log_error ( " Could not parse @stats_list response. " ) ;
2015-09-06 01:56:30 +03:00
goto bad ;
2015-08-05 12:40:00 +03:00
}
dm_task_destroy ( dmt ) ;
return 1 ;
2015-09-06 01:56:30 +03:00
bad :
2015-08-05 12:40:00 +03:00
dm_task_destroy ( dmt ) ;
return 0 ;
}
2015-08-19 22:39:10 +03:00
/*
* Parse histogram data returned from a @ stats_print operation .
*/
static int _stats_parse_histogram ( struct dm_pool * mem , char * hist_str ,
struct dm_histogram * * histogram ,
struct dm_stats_region * region )
{
2016-02-22 16:11:02 +03:00
static const char _valid_chars [ ] = " 0123456789: " ;
2016-02-22 13:14:30 +03:00
struct dm_histogram * bounds = region - > bounds ;
struct dm_histogram hist = {
. nr_bins = region - > bounds - > nr_bins
} ;
2015-09-07 22:08:10 +03:00
const char * c , * v , * val_start ;
2015-08-19 22:39:10 +03:00
struct dm_histogram_bin cur ;
2015-09-07 22:08:10 +03:00
uint64_t sum = 0 , this_val ;
char * endptr = NULL ;
2015-08-19 22:39:10 +03:00
int bin = 0 ;
c = hist_str ;
2015-09-07 13:52:54 +03:00
if ( ! dm_pool_begin_object ( mem , sizeof ( cur ) ) )
return_0 ;
2015-08-19 22:39:10 +03:00
2015-09-07 13:44:53 +03:00
if ( ! dm_pool_grow_object ( mem , & hist , sizeof ( hist ) ) )
goto_bad ;
2015-08-19 22:39:10 +03:00
do {
memset ( & cur , 0 , sizeof ( cur ) ) ;
2015-09-06 01:56:30 +03:00
for ( v = _valid_chars ; * v ; v + + )
2015-08-19 22:39:10 +03:00
if ( * c = = * v )
break ;
2015-09-07 13:44:53 +03:00
if ( ! * v )
2015-08-19 22:39:10 +03:00
goto badchar ;
2015-09-07 13:44:53 +03:00
if ( * c = = ' , ' )
goto badchar ;
else {
2015-09-07 22:08:10 +03:00
val_start = c ;
endptr = NULL ;
2015-08-19 22:39:10 +03:00
this_val = strtoull ( val_start , & endptr , 10 ) ;
if ( ! endptr ) {
log_error ( " Could not parse histogram value. " ) ;
2015-09-07 13:44:53 +03:00
goto bad ;
2015-08-19 22:39:10 +03:00
}
c = endptr ; /* Advance to colon, or end. */
if ( * c = = ' : ' )
c + + ;
2015-09-07 13:44:53 +03:00
else if ( * c & ( * c ! = ' \n ' ) )
2015-08-19 22:39:10 +03:00
/* Expected ':', '\n', or NULL. */
goto badchar ;
if ( * c = = ' : ' )
c + + ;
cur . upper = bounds - > bins [ bin ] . upper ;
cur . count = this_val ;
sum + = this_val ;
2015-09-07 13:44:53 +03:00
if ( ! dm_pool_grow_object ( mem , & cur , sizeof ( cur ) ) )
goto_bad ;
2015-08-19 22:39:10 +03:00
bin + + ;
}
} while ( * c & & ( * c ! = ' \n ' ) ) ;
2016-02-22 13:14:30 +03:00
log_debug ( " Added region histogram data with %d entries. " , hist . nr_bins ) ;
2015-08-19 22:39:10 +03:00
* histogram = dm_pool_end_object ( mem ) ;
( * histogram ) - > sum = sum ;
return 1 ;
badchar :
log_error ( " Invalid character in histogram data: '%c' (0x%x) " , * c , * c ) ;
2015-09-07 13:44:53 +03:00
bad :
dm_pool_abandon_object ( mem ) ;
2015-08-19 22:39:10 +03:00
return 0 ;
}
static int _stats_parse_region ( struct dm_stats * dms , const char * resp ,
2015-08-05 12:40:00 +03:00
struct dm_stats_region * region ,
uint64_t timescale )
{
2015-08-19 22:39:10 +03:00
struct dm_histogram * hist = NULL ;
struct dm_pool * mem = dms - > mem ;
2015-09-07 22:08:10 +03:00
struct dm_stats_counters cur ;
2015-08-05 12:40:00 +03:00
FILE * stats_rows = NULL ;
2015-11-08 19:21:22 +03:00
uint64_t start = 0 , len = 0 ;
2016-06-16 21:40:43 +03:00
char row [ STATS_ROW_BUF_LEN ] ;
2015-08-05 12:40:00 +03:00
int r ;
if ( ! resp ) {
log_error ( " Could not parse empty @stats_print response. " ) ;
return 0 ;
}
region - > start = UINT64_MAX ;
if ( ! dm_pool_begin_object ( mem , 512 ) )
2015-09-06 01:56:30 +03:00
goto_bad ;
2015-08-05 12:40:00 +03:00
/*
* dm_task_get_message_response ( ) returns a ' const char * ' but
* since fmemopen also permits " w " it expects a ' char * ' .
*/
stats_rows = fmemopen ( ( char * ) resp , strlen ( resp ) , " r " ) ;
if ( ! stats_rows )
2015-09-06 01:56:30 +03:00
goto_bad ;
2015-08-05 12:40:00 +03:00
/*
* Output format for each step - sized area of a region :
*
* < start_sector > + < length > counters
*
* The first 11 counters have the same meaning as
* / sys / block / * / stat or / proc / diskstats .
*
* Please refer to Documentation / iostats . txt for details .
*
* 1. the number of reads completed
* 2. the number of reads merged
* 3. the number of sectors read
* 4. the number of milliseconds spent reading
* 5. the number of writes completed
* 6. the number of writes merged
* 7. the number of sectors written
* 8. the number of milliseconds spent writing
* 9. the number of I / Os currently in progress
* 10. the number of milliseconds spent doing I / Os
* 11. the weighted number of milliseconds spent doing I / Os
*
* Additional counters :
* 12. the total time spent reading in milliseconds
* 13. the total time spent writing in milliseconds
*
*/
while ( fgets ( row , sizeof ( row ) , stats_rows ) ) {
r = sscanf ( row , FMTu64 " + " FMTu64 /* start+len */
/* reads */
FMTu64 " " FMTu64 " " FMTu64 " " FMTu64 " "
/* writes */
FMTu64 " " FMTu64 " " FMTu64 " " FMTu64 " "
/* in flight & io nsecs */
FMTu64 " " FMTu64 " " FMTu64 " "
/* tot read/write nsecs */
FMTu64 " " FMTu64 , & start , & len ,
& cur . reads , & cur . reads_merged , & cur . read_sectors ,
& cur . read_nsecs ,
& cur . writes , & cur . writes_merged , & cur . write_sectors ,
& cur . write_nsecs ,
& cur . io_in_progress ,
& cur . io_nsecs , & cur . weighted_io_nsecs ,
& cur . total_read_nsecs , & cur . total_write_nsecs ) ;
if ( r ! = 15 ) {
log_error ( " Could not parse @stats_print row. " ) ;
2015-09-06 01:56:30 +03:00
goto bad ;
2015-08-05 12:40:00 +03:00
}
/* scale time values up if needed */
if ( timescale ! = 1 ) {
cur . read_nsecs * = timescale ;
cur . write_nsecs * = timescale ;
cur . io_nsecs * = timescale ;
cur . weighted_io_nsecs * = timescale ;
cur . total_read_nsecs * = timescale ;
cur . total_write_nsecs * = timescale ;
}
2015-08-19 22:39:10 +03:00
if ( region - > bounds ) {
/* Find first histogram separator. */
char * hist_str = strchr ( row , ' : ' ) ;
if ( ! hist_str ) {
log_error ( " Could not parse histogram value. " ) ;
2015-09-06 01:56:30 +03:00
goto bad ;
2015-08-19 22:39:10 +03:00
}
/* Find space preceding histogram. */
while ( hist_str & & * ( hist_str - 1 ) ! = ' ' )
hist_str - - ;
/* Use a separate pool for histogram objects since we
* are growing the area table and each area ' s histogram
* table simultaneously .
*/
if ( ! _stats_parse_histogram ( dms - > hist_mem , hist_str ,
& hist , region ) )
2015-09-06 01:56:30 +03:00
goto_bad ;
2015-08-19 22:39:10 +03:00
hist - > dms = dms ;
hist - > region = region ;
}
cur . histogram = hist ;
2015-09-06 01:56:30 +03:00
if ( ! dm_pool_grow_object ( mem , & cur , sizeof ( cur ) ) )
goto_bad ;
2015-08-19 22:39:10 +03:00
2015-08-05 12:40:00 +03:00
if ( region - > start = = UINT64_MAX ) {
region - > start = start ;
region - > step = len ; /* area size is always uniform. */
}
}
2016-04-22 12:35:07 +03:00
if ( region - > start = = UINT64_MAX )
/* no area data read from @stats_print */
goto bad ;
2015-08-05 12:40:00 +03:00
region - > len = ( start + len ) - region - > start ;
region - > timescale = timescale ;
region - > counters = dm_pool_end_object ( mem ) ;
2015-08-10 21:23:41 +03:00
if ( fclose ( stats_rows ) )
stack ;
2015-08-05 12:40:00 +03:00
return 1 ;
2015-09-06 01:56:30 +03:00
bad :
2015-08-05 12:40:00 +03:00
if ( stats_rows )
2015-09-06 01:56:30 +03:00
if ( fclose ( stats_rows ) )
2015-08-10 21:23:41 +03:00
stack ;
2015-08-05 12:40:00 +03:00
dm_pool_abandon_object ( mem ) ;
2015-09-06 01:56:30 +03:00
2015-08-05 12:40:00 +03:00
return 0 ;
}
2016-06-19 15:30:46 +03:00
static void _stats_walk_next_present ( const struct dm_stats * dms ,
uint64_t * flags ,
uint64_t * cur_r , uint64_t * cur_a ,
uint64_t * cur_g )
2015-08-05 12:40:00 +03:00
{
2016-06-19 15:30:46 +03:00
struct dm_stats_region * cur = NULL ;
/* start of walk: region loop advances *cur_r to 0. */
if ( * cur_r ! = DM_STATS_REGION_NOT_PRESENT )
cur = & dms - > regions [ * cur_r ] ;
2015-08-05 12:40:00 +03:00
2016-06-19 15:30:46 +03:00
/* within current region? */
if ( cur & & ( * flags & DM_STATS_WALK_AREA ) ) {
if ( + + ( * cur_a ) < _nr_areas_region ( cur ) )
return ;
else
* cur_a = 0 ;
}
/* advance to next present, non-skipped region or end */
while ( + + ( * cur_r ) < = dms - > max_region ) {
cur = & dms - > regions [ * cur_r ] ;
if ( ! _stats_region_present ( cur ) )
continue ;
if ( ( * flags & DM_STATS_WALK_SKIP_SINGLE_AREA ) )
if ( ! ( * flags & DM_STATS_WALK_AREA ) )
if ( _nr_areas_region ( cur ) < 2 )
continue ;
/* matching region found */
break ;
}
return ;
}
static void _stats_walk_next ( const struct dm_stats * dms , uint64_t * flags ,
uint64_t * cur_r , uint64_t * cur_a , uint64_t * cur_g )
{
2015-08-05 12:40:00 +03:00
if ( ! dms | | ! dms - > regions )
return ;
2016-06-19 15:30:46 +03:00
if ( * flags & DM_STATS_WALK_AREA ) {
/* advance to next area, region, or end */
_stats_walk_next_present ( dms , flags , cur_r , cur_a , cur_g ) ;
return ;
}
2015-08-05 12:40:00 +03:00
2016-06-19 15:30:46 +03:00
if ( * flags & DM_STATS_WALK_REGION ) {
/* enable region aggregation */
* cur_a = DM_STATS_WALK_REGION ;
_stats_walk_next_present ( dms , flags , cur_r , cur_a , cur_g ) ;
return ;
}
2015-08-05 12:40:00 +03:00
2016-06-19 15:30:46 +03:00
if ( * flags & DM_STATS_WALK_GROUP ) {
/* enable group aggregation */
* cur_r = * cur_a = DM_STATS_WALK_GROUP ;
while ( ! _stats_group_id_present ( dms , + + ( * cur_g ) )
& & ( * cur_g ) < dms - > max_region + 1 )
; /* advance to next present group or end */
return ;
2015-08-05 12:40:00 +03:00
}
2016-06-19 15:30:46 +03:00
log_error ( " stats_walk_next called with empty walk flags " ) ;
2015-08-05 12:40:00 +03:00
}
2016-06-19 15:30:46 +03:00
static void _group_walk_start ( const struct dm_stats * dms , uint64_t * flags ,
uint64_t * cur_r , uint64_t * cur_a , uint64_t * cur_g )
2015-08-05 12:40:00 +03:00
{
2016-06-19 15:30:46 +03:00
if ( ! ( * flags & DM_STATS_WALK_GROUP ) )
return ;
* cur_a = * cur_r = DM_STATS_WALK_GROUP ;
* cur_g = 0 ;
/* advance to next present group or end */
while ( ( * cur_g ) < = dms - > max_region ) {
if ( _stats_region_is_grouped ( dms , * cur_g ) )
break ;
( * cur_g ) + + ;
}
if ( * cur_g > dms - > max_region )
/* no groups to walk */
* flags & = ~ DM_STATS_WALK_GROUP ;
}
static void _stats_walk_start ( const struct dm_stats * dms , uint64_t * flags ,
uint64_t * cur_r , uint64_t * cur_a ,
uint64_t * cur_g )
{
log_debug ( " starting stats walk with %s %s %s %s " ,
( * flags & DM_STATS_WALK_AREA ) ? " AREA " : " " ,
( * flags & DM_STATS_WALK_REGION ) ? " REGION " : " " ,
( * flags & DM_STATS_WALK_GROUP ) ? " GROUP " : " " ,
( * flags & DM_STATS_WALK_SKIP_SINGLE_AREA ) ? " SKIP " : " " ) ;
if ( ! dms - > regions )
2015-08-05 12:40:00 +03:00
return ;
2016-06-19 15:30:46 +03:00
if ( ! ( * flags & ( DM_STATS_WALK_AREA | DM_STATS_WALK_REGION ) ) )
return _group_walk_start ( dms , flags , cur_r , cur_a , cur_g ) ;
/* initialise cursor state */
2015-08-05 12:40:00 +03:00
* cur_a = 0 ;
2016-06-19 15:30:46 +03:00
* cur_r = DM_STATS_REGION_NOT_PRESENT ;
* cur_g = DM_STATS_GROUP_NOT_PRESENT ;
if ( ! ( * flags & DM_STATS_WALK_AREA ) )
* cur_a = DM_STATS_WALK_REGION ;
2015-08-05 12:40:00 +03:00
2016-06-19 15:30:46 +03:00
/* advance to first present, non-skipped region */
_stats_walk_next_present ( dms , flags , cur_r , cur_a , cur_g ) ;
}
# define DM_STATS_WALK_MASK (DM_STATS_WALK_AREA \
| DM_STATS_WALK_REGION \
| DM_STATS_WALK_GROUP \
| DM_STATS_WALK_SKIP_SINGLE_AREA )
int dm_stats_walk_init ( struct dm_stats * dms , uint64_t flags )
{
if ( ! dms )
return_0 ;
if ( flags & ~ DM_STATS_WALK_MASK ) {
log_error ( " Unknown value in walk flags: 0x " FMTx64 ,
2016-07-05 22:45:24 +03:00
( uint64_t ) ( flags & ~ DM_STATS_WALK_MASK ) ) ;
2016-06-19 15:30:46 +03:00
return 0 ;
}
dms - > walk_flags = flags ;
log_debug ( " dm_stats_walk_init: initialised flags to " FMTx64 , flags ) ;
return 1 ;
2015-08-05 12:40:00 +03:00
}
void dm_stats_walk_start ( struct dm_stats * dms )
{
2016-06-19 15:30:46 +03:00
if ( ! dms | | ! dms - > regions )
return ;
dms - > cur_flags = dms - > walk_flags ;
_stats_walk_start ( dms , & dms - > cur_flags ,
& dms - > cur_region , & dms - > cur_area ,
& dms - > cur_group ) ;
2015-08-05 12:40:00 +03:00
}
void dm_stats_walk_next ( struct dm_stats * dms )
{
2016-06-19 15:30:46 +03:00
_stats_walk_next ( dms , & dms - > cur_flags ,
& dms - > cur_region , & dms - > cur_area ,
& dms - > cur_group ) ;
2015-08-05 12:40:00 +03:00
}
void dm_stats_walk_next_region ( struct dm_stats * dms )
{
2016-06-19 15:30:46 +03:00
dms - > cur_flags & = ~ DM_STATS_WALK_AREA ;
_stats_walk_next ( dms , & dms - > cur_flags ,
& dms - > cur_region , & dms - > cur_area ,
& dms - > cur_group ) ;
2015-08-05 12:40:00 +03:00
}
2016-06-19 15:30:46 +03:00
/*
* Return 1 if any regions remain that are present and not skipped
* by the current walk flags or 0 otherwise .
*/
static uint64_t _stats_walk_any_unskipped ( const struct dm_stats * dms ,
uint64_t * flags ,
uint64_t * cur_r , uint64_t * cur_a )
2015-08-05 12:40:00 +03:00
{
2016-06-19 15:30:46 +03:00
struct dm_stats_region * region ;
uint64_t i ;
if ( * cur_r > dms - > max_region )
return 0 ;
2015-08-05 12:40:00 +03:00
2016-06-19 15:30:46 +03:00
for ( i = * cur_r ; i < = dms - > max_region ; i + + ) {
region = & dms - > regions [ i ] ;
if ( ! _stats_region_present ( region ) )
continue ;
if ( ( * flags & DM_STATS_WALK_SKIP_SINGLE_AREA )
& & ! ( * flags & DM_STATS_WALK_AREA ) )
if ( _nr_areas_region ( region ) < 2 )
continue ;
return 1 ;
}
return 0 ;
}
static void _stats_walk_end_areas ( const struct dm_stats * dms , uint64_t * flags ,
uint64_t * cur_r , uint64_t * cur_a ,
uint64_t * cur_g )
{
int end = ! _stats_walk_any_unskipped ( dms , flags , cur_r , cur_a ) ;
if ( ! ( * flags & DM_STATS_WALK_AREA ) )
return ;
if ( ! end )
return ;
* flags & = ~ DM_STATS_WALK_AREA ;
if ( * flags & DM_STATS_WALK_REGION ) {
/* start region walk */
* cur_a = DM_STATS_WALK_REGION ;
* cur_r = DM_STATS_REGION_NOT_PRESENT ;
_stats_walk_next_present ( dms , flags , cur_r , cur_a , cur_g ) ;
2016-07-06 11:04:13 +03:00
if ( ! _stats_walk_any_unskipped ( dms , flags , cur_r , cur_a ) ) {
2016-06-19 15:30:46 +03:00
/* no more regions */
* flags & = ~ DM_STATS_WALK_REGION ;
if ( ! ( * flags & DM_STATS_WALK_GROUP ) )
* cur_r = dms - > max_region ;
2016-07-06 11:04:13 +03:00
}
2016-06-19 15:30:46 +03:00
}
if ( * flags & DM_STATS_WALK_REGION )
return ;
if ( * flags & DM_STATS_WALK_GROUP )
_group_walk_start ( dms , flags , cur_r , cur_a , cur_g ) ;
}
static int _stats_walk_end ( const struct dm_stats * dms , uint64_t * flags ,
uint64_t * cur_r , uint64_t * cur_a , uint64_t * cur_g )
{
if ( * flags & DM_STATS_WALK_AREA ) {
_stats_walk_end_areas ( dms , flags , cur_r , cur_a , cur_g ) ;
goto out ;
}
if ( * flags & DM_STATS_WALK_REGION ) {
if ( ! _stats_walk_any_unskipped ( dms , flags , cur_r , cur_a ) ) {
* flags & = ~ DM_STATS_WALK_REGION ;
_group_walk_start ( dms , flags , cur_r , cur_a , cur_g ) ;
}
goto out ;
}
2015-08-05 12:40:00 +03:00
2016-06-19 15:30:46 +03:00
if ( * flags & DM_STATS_WALK_GROUP ) {
2016-09-16 15:08:30 +03:00
if ( * cur_g < = dms - > max_region )
2016-06-19 15:30:46 +03:00
goto out ;
* flags & = ~ DM_STATS_WALK_GROUP ;
}
out :
return ! ( * flags & ~ DM_STATS_WALK_SKIP_SINGLE_AREA ) ;
2015-08-05 12:40:00 +03:00
}
int dm_stats_walk_end ( struct dm_stats * dms )
{
2016-09-27 19:46:52 +03:00
if ( ! dms )
2016-09-27 16:35:22 +03:00
return 1 ;
2016-06-19 15:30:46 +03:00
if ( _stats_walk_end ( dms , & dms - > cur_flags ,
& dms - > cur_region , & dms - > cur_area ,
& dms - > cur_group ) ) {
dms - > cur_flags = dms - > walk_flags ;
return 1 ;
}
return 0 ;
}
dm_stats_obj_type_t dm_stats_object_type ( const struct dm_stats * dms ,
uint64_t region_id ,
uint64_t area_id )
{
uint64_t group_id ;
region_id = ( region_id = = DM_STATS_REGION_CURRENT )
? dms - > cur_region : region_id ;
area_id = ( area_id = = DM_STATS_AREA_CURRENT )
? dms - > cur_area : area_id ;
if ( region_id = = DM_STATS_REGION_NOT_PRESENT )
/* no region */
return DM_STATS_OBJECT_TYPE_NONE ;
if ( region_id & DM_STATS_WALK_GROUP ) {
if ( region_id = = DM_STATS_WALK_GROUP )
/* indirect group_id from cursor */
group_id = dms - > cur_group ;
else
/* immediate group_id encoded in region_id */
group_id = region_id & ~ DM_STATS_WALK_GROUP ;
if ( ! _stats_group_id_present ( dms , group_id ) )
return DM_STATS_OBJECT_TYPE_NONE ;
return DM_STATS_OBJECT_TYPE_GROUP ;
}
if ( region_id > dms - > max_region )
/* end of table */
return DM_STATS_OBJECT_TYPE_NONE ;
if ( area_id & DM_STATS_WALK_REGION )
/* aggregate region */
return DM_STATS_OBJECT_TYPE_REGION ;
/* plain region_id and area_id */
return DM_STATS_OBJECT_TYPE_AREA ;
}
dm_stats_obj_type_t dm_stats_current_object_type ( const struct dm_stats * dms )
{
/* dm_stats_object_type will decode region/area */
return dm_stats_object_type ( dms ,
DM_STATS_REGION_CURRENT ,
DM_STATS_AREA_CURRENT ) ;
2015-08-05 12:40:00 +03:00
}
uint64_t dm_stats_get_region_nr_areas ( const struct dm_stats * dms ,
uint64_t region_id )
{
2016-06-19 15:30:46 +03:00
struct dm_stats_region * region = NULL ;
/* groups or aggregate regions cannot be subdivided */
if ( region_id & DM_STATS_WALK_GROUP )
return 1 ;
region = & dms - > regions [ region_id ] ;
2015-08-05 12:40:00 +03:00
return _nr_areas_region ( region ) ;
}
uint64_t dm_stats_get_current_nr_areas ( const struct dm_stats * dms )
{
2016-06-19 15:30:46 +03:00
/* groups or aggregate regions cannot be subdivided */
if ( dms - > cur_region & DM_STATS_WALK_GROUP )
return 1 ;
2015-08-05 12:40:00 +03:00
return dm_stats_get_region_nr_areas ( dms , dms - > cur_region ) ;
}
uint64_t dm_stats_get_nr_areas ( const struct dm_stats * dms )
{
2016-06-19 15:30:46 +03:00
uint64_t nr_areas = 0 , flags = DM_STATS_WALK_AREA ;
2015-08-05 12:40:00 +03:00
/* use a separate cursor */
2016-06-19 15:30:46 +03:00
uint64_t cur_region = 0 , cur_area = 0 , cur_group = 0 ;
/* no regions to visit? */
if ( ! dms - > regions )
return 0 ;
2015-08-05 12:40:00 +03:00
2016-06-19 15:30:46 +03:00
flags = DM_STATS_WALK_AREA ;
_stats_walk_start ( dms , & flags , & cur_region , & cur_area , & cur_group ) ;
2015-08-05 12:40:00 +03:00
do {
nr_areas + = dm_stats_get_current_nr_areas ( dms ) ;
2016-06-19 15:30:46 +03:00
_stats_walk_next ( dms , & flags ,
& cur_region , & cur_area ,
& cur_group ) ;
} while ( ! _stats_walk_end ( dms , & flags ,
& cur_region , & cur_area ,
& cur_group ) ) ;
2015-08-05 12:40:00 +03:00
return nr_areas ;
}
2016-02-29 20:52:29 +03:00
int dm_stats_group_present ( const struct dm_stats * dms , uint64_t group_id )
{
return _stats_group_id_present ( dms , group_id ) ;
}
2015-08-19 22:39:10 +03:00
int dm_stats_get_region_nr_histogram_bins ( const struct dm_stats * dms ,
uint64_t region_id )
{
region_id = ( region_id = = DM_STATS_REGION_CURRENT )
? dms - > cur_region : region_id ;
2016-06-19 15:30:46 +03:00
/* FIXME: support group histograms if all region bounds match */
if ( region_id & DM_STATS_WALK_GROUP )
return 0 ;
2015-08-19 22:39:10 +03:00
if ( ! dms - > regions [ region_id ] . bounds )
2016-06-19 15:30:46 +03:00
return 0 ;
2015-08-19 22:39:10 +03:00
return dms - > regions [ region_id ] . bounds - > nr_bins ;
}
2016-02-29 20:52:29 +03:00
/*
* Fill buf with a list of set regions in the regions bitmap . Consecutive
* ranges of set region IDs are output using " M-N " range notation .
*
* The number of bytes consumed is returned or zero on error .
*/
static size_t _stats_group_tag_fill ( const struct dm_stats * dms ,
dm_bitset_t regions ,
char * buf , size_t buflen )
{
int i , j , r , next , last = 0 ;
size_t used = 0 ;
2016-12-13 13:32:29 +03:00
last = dm_bit_get_last ( regions ) ;
2016-02-29 20:52:29 +03:00
i = dm_bit_get_first ( regions ) ;
for ( ; i > = 0 ; i = dm_bit_get_next ( regions , i ) ) {
/* find range end */
j = i ;
do
next = j + 1 ;
while ( ( j = dm_bit_get_next ( regions , j ) ) = = next ) ;
/* set to last set bit */
j = next - 1 ;
/* handle range vs. single region */
if ( i ! = j )
r = dm_snprintf ( buf , buflen , FMTu64 " - " FMTu64 " %s " ,
( uint64_t ) i , ( uint64_t ) j ,
( j = = last ) ? " " : " , " ) ;
else
r = dm_snprintf ( buf , buflen , FMTu64 " %s " , ( uint64_t ) i ,
( i = = last ) ? " " : " , " ) ;
if ( r < 0 )
goto_bad ;
i = next ; /* skip handled bits if in range */
buf + = r ;
used + = r ;
}
return used ;
bad :
log_error ( " Could not format group list. " ) ;
return 0 ;
}
/*
* Calculate the space required to hold a string description of the group
* described by the regions bitset using comma separated list in range
* notation ( " A,B,C,M-N " ) .
*/
static size_t _stats_group_tag_len ( const struct dm_stats * dms ,
dm_bitset_t regions )
{
2016-12-13 17:31:39 +03:00
int64_t i , j , next , nr_regions = 0 ;
2016-02-29 20:52:29 +03:00
size_t buflen = 0 , id_len = 0 ;
/* check region ids and find last set bit */
i = dm_bit_get_first ( regions ) ;
for ( ; i > = 0 ; i = dm_bit_get_next ( regions , i ) ) {
/* length of region_id or range start in characters */
id_len = ( i ) ? 1 + ( size_t ) log10 ( i ) : 1 ;
buflen + = id_len ;
j = i ;
do
next = j + 1 ;
while ( ( j = dm_bit_get_next ( regions , j ) ) = = next ) ;
/* set to last set bit */
j = next - 1 ;
nr_regions + = j - i + 1 ;
/* handle range */
if ( i ! = j ) {
/* j is always > i, which is always >= 0 */
id_len = 1 + ( size_t ) log10 ( j ) ;
buflen + = id_len + 1 ; /* range end plus "-" */
}
buflen + + ;
i = next ; /* skip bits if handling range */
}
return buflen ;
}
/*
* Build a DMS_GROUP = " ... " tag for the group specified by group_id ,
* to be stored in the corresponding region ' s aux_data field .
*/
static char * _build_group_tag ( struct dm_stats * dms , uint64_t group_id )
{
char * aux_string , * buf ;
dm_bitset_t regions ;
const char * alias ;
size_t buflen = 0 ;
int r ;
regions = dms - > groups [ group_id ] . regions ;
alias = dms - > groups [ group_id ] . alias ;
buflen = _stats_group_tag_len ( dms , regions ) ;
if ( ! buflen )
return_0 ;
buflen + = DMS_GROUP_TAG_LEN ;
buflen + = 1 + ( alias ? strlen ( alias ) : 0 ) ; /* 'alias:' */
buf = aux_string = dm_malloc ( buflen ) ;
if ( ! buf ) {
log_error ( " Could not allocate memory for aux_data string. " ) ;
return NULL ;
}
if ( ! dm_strncpy ( buf , DMS_GROUP_TAG , DMS_GROUP_TAG_LEN + 1 ) )
goto_bad ;
buf + = DMS_GROUP_TAG_LEN ;
buflen - = DMS_GROUP_TAG_LEN ;
r = dm_snprintf ( buf , buflen , " %s%c " , alias ? alias : " " , DMS_GROUP_SEP ) ;
if ( r < 0 )
goto_bad ;
buf + = r ;
buflen - = r ;
r = _stats_group_tag_fill ( dms , regions , buf , buflen ) ;
if ( ! r )
goto_bad ;
return aux_string ;
bad :
log_error ( " Could not format group aux_data. " ) ;
dm_free ( aux_string ) ;
return NULL ;
}
/*
* Store updated aux_data for a region . The aux_data is passed to the
* kernel using the @ stats_set_aux message . Any required group tag is
* generated from the current group table and included in the message .
*/
static int _stats_set_aux ( struct dm_stats * dms ,
uint64_t region_id , const char * aux_data )
{
const char * group_tag = NULL ;
struct dm_task * dmt = NULL ;
2016-06-16 21:40:43 +03:00
char msg [ STATS_MSG_BUF_LEN ] ;
2016-02-29 20:52:29 +03:00
/* group data required? */
if ( _stats_group_id_present ( dms , region_id ) ) {
group_tag = _build_group_tag ( dms , region_id ) ;
if ( ! group_tag ) {
log_error ( " Could not build group descriptor for "
" region ID " FMTu64 , region_id ) ;
2016-11-25 15:46:06 +03:00
goto bad ;
2016-02-29 20:52:29 +03:00
}
}
if ( ! dm_snprintf ( msg , sizeof ( msg ) , " @stats_set_aux " FMTu64 " %s%s%s " ,
region_id , ( group_tag ) ? group_tag : " " ,
( group_tag ) ? DMS_AUX_SEP : " " ,
( strlen ( aux_data ) ) ? aux_data : " - " ) ) {
log_error ( " Could not prepare @stats_set_aux message " ) ;
goto bad ;
}
if ( ! ( dmt = _stats_send_message ( dms , msg ) ) )
goto_bad ;
dm_free ( ( char * ) group_tag ) ;
/* no response to a @stats_set_aux message */
dm_task_destroy ( dmt ) ;
return 1 ;
bad :
dm_free ( ( char * ) group_tag ) ;
return 0 ;
}
2016-06-16 21:40:43 +03:00
/*
* Maximum length of a " start+end " range string :
* Two 20 digit uint64_t , ' + ' , and NULL .
*/
# define RANGE_LEN 42
2015-08-20 13:55:06 +03:00
static int _stats_create_region ( struct dm_stats * dms , uint64_t * region_id ,
uint64_t start , uint64_t len , int64_t step ,
2015-08-19 22:39:10 +03:00
int precise , const char * hist_arg ,
2015-08-20 13:55:06 +03:00
const char * program_id , const char * aux_data )
2015-08-05 12:40:00 +03:00
{
2016-06-16 21:40:43 +03:00
char msg [ STATS_MSG_BUF_LEN ] , range [ RANGE_LEN ] , * endptr = NULL ;
2015-08-05 12:40:00 +03:00
const char * err_fmt = " Could not prepare @stats_create %s. " ;
2015-08-20 13:55:06 +03:00
const char * precise_str = PRECISE_ARG ;
const char * resp , * opt_args = NULL ;
2015-09-07 22:08:10 +03:00
struct dm_task * dmt = NULL ;
int r = 0 , nr_opt = 0 ;
2015-08-05 12:40:00 +03:00
if ( ! _stats_bound ( dms ) )
return_0 ;
if ( ! program_id | | ! strlen ( program_id ) )
program_id = dms - > program_id ;
if ( start | | len ) {
if ( ! dm_snprintf ( range , sizeof ( range ) , FMTu64 " + " FMTu64 ,
start , len ) ) {
log_error ( err_fmt , " range " ) ;
2015-08-20 13:55:06 +03:00
return 0 ;
2015-08-05 12:40:00 +03:00
}
}
2015-08-20 13:55:06 +03:00
if ( precise < 0 )
precise = dms - > precise ;
if ( precise )
nr_opt + + ;
else
precise_str = " " ;
2015-08-19 22:39:10 +03:00
if ( hist_arg )
nr_opt + + ;
else
hist_arg = " " ;
2015-08-20 13:55:06 +03:00
if ( nr_opt ) {
2015-08-19 22:39:10 +03:00
if ( ( dm_asprintf ( ( char * * ) & opt_args , " %d %s %s%s " , nr_opt ,
precise_str ,
( strlen ( hist_arg ) ) ? HISTOGRAM_ARG : " " ,
hist_arg ) ) < 0 ) {
2015-08-20 13:55:06 +03:00
log_error ( err_fmt , PRECISE_ARG " option. " ) ;
return 0 ;
}
} else
opt_args = dm_strdup ( " " ) ;
if ( ! dm_snprintf ( msg , sizeof ( msg ) , " @stats_create %s %s " FMTu64
" %s %s %s " , ( start | | len ) ? range : " - " ,
2015-08-05 12:40:00 +03:00
( step < 0 ) ? " / " : " " ,
2015-08-20 13:55:06 +03:00
( uint64_t ) llabs ( step ) ,
opt_args , program_id , aux_data ) ) {
2015-08-05 12:40:00 +03:00
log_error ( err_fmt , " message " ) ;
2015-08-20 13:55:06 +03:00
dm_free ( ( void * ) opt_args ) ;
return 0 ;
2015-08-05 12:40:00 +03:00
}
if ( ! ( dmt = _stats_send_message ( dms , msg ) ) )
2015-09-06 01:56:30 +03:00
goto_out ;
2015-08-05 12:40:00 +03:00
resp = dm_task_get_message_response ( dmt ) ;
if ( ! resp ) {
log_error ( " Could not parse empty @stats_create response. " ) ;
goto out ;
}
if ( region_id ) {
* region_id = strtoull ( resp , & endptr , 10 ) ;
if ( resp = = endptr )
goto_out ;
}
2015-08-20 13:55:06 +03:00
r = 1 ;
2015-08-05 12:40:00 +03:00
out :
2015-09-06 01:56:30 +03:00
if ( dmt )
2015-08-05 12:40:00 +03:00
dm_task_destroy ( dmt ) ;
2015-08-20 13:55:06 +03:00
dm_free ( ( void * ) opt_args ) ;
2015-09-06 01:56:30 +03:00
2015-08-20 13:55:06 +03:00
return r ;
}
int dm_stats_create_region ( struct dm_stats * dms , uint64_t * region_id ,
uint64_t start , uint64_t len , int64_t step ,
2015-08-19 22:39:10 +03:00
int precise , struct dm_histogram * bounds ,
2016-07-05 17:17:44 +03:00
const char * program_id , const char * user_data )
2015-08-20 13:55:06 +03:00
{
2015-08-19 22:39:10 +03:00
char * hist_arg = NULL ;
int r = 0 ;
/* Nanosecond counters and histograms both need precise_timestamps. */
2015-09-06 01:56:30 +03:00
if ( ( precise | | bounds ) & & ! _stats_check_precise_timestamps ( dms ) )
return_0 ;
2015-08-20 13:55:06 +03:00
2015-08-19 22:39:10 +03:00
if ( bounds ) {
/* _build_histogram_arg enables precise if vals < 1ms. */
if ( ! ( hist_arg = _build_histogram_arg ( bounds , & precise ) ) )
goto_out ;
}
2015-08-20 13:55:06 +03:00
2015-08-19 22:39:10 +03:00
r = _stats_create_region ( dms , region_id , start , len , step ,
2016-07-05 17:17:44 +03:00
precise , hist_arg , program_id , user_data ) ;
2015-08-19 22:39:10 +03:00
dm_free ( hist_arg ) ;
2015-09-06 01:56:30 +03:00
2015-08-19 22:39:10 +03:00
out :
return r ;
2015-08-05 12:40:00 +03:00
}
2016-02-29 20:52:29 +03:00
static void _stats_clear_group_regions ( struct dm_stats * dms , uint64_t group_id )
{
struct dm_stats_group * group ;
uint64_t i ;
group = & dms - > groups [ group_id ] ;
for ( i = dm_bit_get_first ( group - > regions ) ;
i ! = DM_STATS_GROUP_NOT_PRESENT ;
i = dm_bit_get_next ( group - > regions , i ) )
dms - > regions [ i ] . group_id = DM_STATS_GROUP_NOT_PRESENT ;
}
static int _stats_remove_region_id_from_group ( struct dm_stats * dms ,
uint64_t region_id )
{
struct dm_stats_region * region = & dms - > regions [ region_id ] ;
uint64_t group_id = region - > group_id ;
2016-07-01 15:14:41 +03:00
dm_bitset_t regions = dms - > groups [ group_id ] . regions ;
2016-02-29 20:52:29 +03:00
if ( ! _stats_region_is_grouped ( dms , region_id ) )
return_0 ;
dm_bit_clear ( regions , region_id ) ;
/* removing group leader? */
if ( region_id = = group_id ) {
_stats_clear_group_regions ( dms , group_id ) ;
_stats_group_destroy ( & dms - > groups [ group_id ] ) ;
}
return _stats_set_aux ( dms , group_id , dms - > regions [ group_id ] . aux_data ) ;
}
2016-12-10 01:58:25 +03:00
static int _stats_delete_region ( struct dm_stats * dms , uint64_t region_id )
2015-08-05 12:40:00 +03:00
{
2016-06-16 21:40:43 +03:00
char msg [ STATS_MSG_BUF_LEN ] ;
2015-08-05 12:40:00 +03:00
struct dm_task * dmt ;
2016-12-10 01:58:25 +03:00
if ( _stats_region_is_grouped ( dms , region_id ) )
if ( ! _stats_remove_region_id_from_group ( dms , region_id ) ) {
log_error ( " Could not remove region ID " FMTu64 " from "
" group ID " FMTu64 ,
region_id , dms - > regions [ region_id ] . group_id ) ;
return 0 ;
}
if ( ! dm_snprintf ( msg , sizeof ( msg ) , " @stats_delete " FMTu64 , region_id ) ) {
log_error ( " Could not prepare @stats_delete message. " ) ;
return 0 ;
}
dmt = _stats_send_message ( dms , msg ) ;
if ( ! dmt )
return_0 ;
dm_task_destroy ( dmt ) ;
return 1 ;
}
int dm_stats_delete_region ( struct dm_stats * dms , uint64_t region_id )
{
2016-09-27 19:46:52 +03:00
int listed = 0 ;
2015-08-05 12:40:00 +03:00
if ( ! _stats_bound ( dms ) )
return_0 ;
2016-09-27 19:46:52 +03:00
/*
* To correctly delete a region , that may be part of a group , a
* listed handle is required , since the region may need to be
* removed from another region ' s group descriptor ; earlier
* versions of the region deletion interface do not have this
* requirement since there are no dependencies between regions .
*
* Listing a previously unlisted handle has numerous
* side - effects on other calls and operations ( e . g . stats
* walks ) , especially when returning to a function that depends
* on the state of the region table , or statistics cursor .
*
* To avoid changing the semantics of the API , and the need for
* a versioned symbol , maintain a flag indicating when a listing
* has been carried out , and drop the region table before
* returning .
*
* This ensures compatibility with programs compiled against
* earlier versions of libdm .
*/
if ( ! dms - > regions & & ! ( listed = dm_stats_list ( dms , dms - > program_id ) ) ) {
2016-02-29 20:52:29 +03:00
log_error ( " Could not obtain region list while deleting "
" region ID " FMTu64 , region_id ) ;
2016-09-27 19:46:52 +03:00
goto bad ;
2016-02-29 20:52:29 +03:00
}
libdm-stats: fix dm_stats_delete_region() performance
Fix a silly bug in dm_stats_delete_region() that hugely inflates
runtimes when deleting a large number of regions.
For ~50,000 regions this change reduces the runtime from 98s to
6s on my test systems (a ~93% reduction).
The bug exists because dm_stats_delete_region() applies a truth
test to the return value of dm_stats_get_nr_areas(); this is
never correct usage - it will walk the entire region table and
calculate area counts for each region (which is roughly O(n^2)
in the number of regions, as dm_stats_delete_region() is being
called inside a region walk).
Although the individual area calculation is not that costly,
uselessly running anything 2,500,000,000 times over gets a bit
slow.
A much cheaper test (which is always true if the areas check is
true) is to just test dm_stats_get_nr_regions() or dms->regions;
if either is true it implies at least one area exists.
Old:
Performance counter stats for 'dmstats delete --allregions --alldevices':
98117.791458 task-clock (msec) # 1.000 CPUs utilized
127 context-switches # 0.001 K/sec
3 cpu-migrations # 0.000 K/sec
6,631 page-faults # 0.068 K/sec
307,711,724,562 cycles # 3.136 GHz
544,762,959,577 instructions # 1.77 insn per cycle
84,287,824,115 branches # 859.047 M/sec
2,538,875 branch-misses # 0.00% of all branches
98.119578733 seconds time elapsed
New:
Performance counter stats for 'dmstats delete --allregions --alldevices':
6427.251074 task-clock (msec) # 1.000 CPUs utilized
6 context-switches # 0.001 K/sec
0 cpu-migrations # 0.000 K/sec
6,634 page-faults # 0.001 M/sec
21,613,018,724 cycles # 3.363 GHz
3,794,755,445 instructions # 0.18 insn per cycle
852,974,026 branches # 132.712 M/sec
808,625 branch-misses # 0.09% of all branches
6.428953647 seconds time elapsed
2016-12-08 23:55:47 +03:00
if ( ! dm_stats_get_nr_regions ( dms ) ) {
2016-02-29 20:52:29 +03:00
log_error ( " Could not delete region ID " FMTu64 " : "
" no regions found " , region_id ) ;
2016-09-27 19:46:52 +03:00
goto bad ;
2016-02-29 20:52:29 +03:00
}
/* includes invalid and special region_id values */
if ( ! dm_stats_region_present ( dms , region_id ) ) {
log_error ( " Region ID " FMTu64 " does not exist " , region_id ) ;
2016-09-27 19:46:52 +03:00
goto bad ;
2016-02-29 20:52:29 +03:00
}
2016-12-10 01:58:25 +03:00
if ( ! _stats_delete_region ( dms , region_id ) )
2016-09-27 19:46:52 +03:00
goto bad ;
2015-08-05 12:40:00 +03:00
2016-09-27 19:46:52 +03:00
if ( ! listed )
/* wipe region and mark as not present */
_stats_region_destroy ( & dms - > regions [ region_id ] ) ;
else
/* return handle to prior state */
_stats_regions_destroy ( dms ) ;
2016-02-29 20:52:29 +03:00
2015-09-06 01:56:30 +03:00
return 1 ;
2016-09-27 19:46:52 +03:00
bad :
if ( listed )
_stats_regions_destroy ( dms ) ;
return 0 ;
2015-08-05 12:40:00 +03:00
}
int dm_stats_clear_region ( struct dm_stats * dms , uint64_t region_id )
{
2016-06-16 21:40:43 +03:00
char msg [ STATS_MSG_BUF_LEN ] ;
2015-08-05 12:40:00 +03:00
struct dm_task * dmt ;
if ( ! _stats_bound ( dms ) )
return_0 ;
if ( ! dm_snprintf ( msg , sizeof ( msg ) , " @stats_clear " FMTu64 , region_id ) ) {
log_error ( " Could not prepare @stats_clear message. " ) ;
2015-09-06 01:56:30 +03:00
return 0 ;
2015-08-05 12:40:00 +03:00
}
dmt = _stats_send_message ( dms , msg ) ;
2015-09-06 01:56:30 +03:00
2015-08-05 12:40:00 +03:00
if ( ! dmt )
2015-09-06 01:56:30 +03:00
return_0 ;
2015-08-05 12:40:00 +03:00
dm_task_destroy ( dmt ) ;
2015-09-06 01:56:30 +03:00
return 1 ;
2015-08-05 12:40:00 +03:00
}
static struct dm_task * _stats_print_region ( struct dm_stats * dms ,
uint64_t region_id , unsigned start_line ,
unsigned num_lines , unsigned clear )
{
/* @stats_print[_clear] <region_id> [<start_line> <num_lines>] */
const char * err_fmt = " Could not prepare @stats_print %s. " ;
2016-06-16 21:40:43 +03:00
char msg [ STATS_MSG_BUF_LEN ] , lines [ RANGE_LEN ] ;
2015-09-07 22:08:10 +03:00
struct dm_task * dmt = NULL ;
2015-08-05 12:40:00 +03:00
if ( start_line | | num_lines )
if ( ! dm_snprintf ( lines , sizeof ( lines ) ,
2015-11-09 11:33:56 +03:00
" %u %u " , start_line , num_lines ) ) {
2015-08-05 12:40:00 +03:00
log_error ( err_fmt , " row specification " ) ;
2015-09-06 01:56:30 +03:00
return NULL ;
2015-08-05 12:40:00 +03:00
}
2015-11-09 11:33:56 +03:00
if ( ! dm_snprintf ( msg , sizeof ( msg ) , " @stats_print%s " FMTu64 " %s " ,
( clear ) ? " _clear " : " " ,
2015-08-05 12:40:00 +03:00
region_id , ( start_line | | num_lines ) ? lines : " " ) ) {
log_error ( err_fmt , " message " ) ;
2015-09-06 01:56:30 +03:00
return NULL ;
2015-08-05 12:40:00 +03:00
}
if ( ! ( dmt = _stats_send_message ( dms , msg ) ) )
2015-09-06 01:56:30 +03:00
return_NULL ;
2015-08-05 12:40:00 +03:00
return dmt ;
}
char * dm_stats_print_region ( struct dm_stats * dms , uint64_t region_id ,
unsigned start_line , unsigned num_lines ,
unsigned clear )
{
char * resp = NULL ;
struct dm_task * dmt = NULL ;
2015-11-09 11:50:46 +03:00
const char * response ;
2015-08-05 12:40:00 +03:00
if ( ! _stats_bound ( dms ) )
return_0 ;
2016-06-19 15:30:46 +03:00
/*
* FIXME : ' print ' can be emulated for groups or aggregate regions
* by populating the handle and emitting aggregate counter data
* in the kernel print format .
*/
if ( region_id = = DM_STATS_WALK_GROUP )
return_0 ;
2015-08-05 12:40:00 +03:00
dmt = _stats_print_region ( dms , region_id ,
start_line , num_lines , clear ) ;
if ( ! dmt )
2015-09-06 01:56:30 +03:00
return_0 ;
2015-08-05 12:40:00 +03:00
2015-11-09 11:50:46 +03:00
if ( ! ( response = dm_task_get_message_response ( dmt ) ) )
2015-11-10 23:34:31 +03:00
goto_out ;
2015-08-05 12:40:00 +03:00
2015-11-10 23:34:31 +03:00
if ( ! ( resp = dm_pool_strdup ( dms - > mem , response ) ) )
2015-08-05 12:40:00 +03:00
log_error ( " Could not allocate memory for response buffer. " ) ;
2015-11-10 23:34:31 +03:00
out :
dm_task_destroy ( dmt ) ;
2015-08-05 12:40:00 +03:00
return resp ;
}
void dm_stats_buffer_destroy ( struct dm_stats * dms , char * buffer )
{
dm_pool_free ( dms - > mem , buffer ) ;
}
uint64_t dm_stats_get_nr_regions ( const struct dm_stats * dms )
{
2016-07-05 20:33:02 +03:00
if ( ! dms )
2015-09-06 01:56:30 +03:00
return_0 ;
2016-07-05 20:33:02 +03:00
if ( ! dms - > regions )
return 0 ;
2015-08-05 12:40:00 +03:00
return dms - > nr_regions ;
}
2016-02-29 20:52:29 +03:00
uint64_t dm_stats_get_nr_groups ( const struct dm_stats * dms )
{
uint64_t group_id , nr_groups = 0 ;
if ( ! dms )
return_0 ;
/* no regions or groups? */
if ( ! dms - > regions | | ! dms - > groups )
return 0 ;
for ( group_id = 0 ; group_id < = dms - > max_region ; group_id + + )
if ( dms - > groups [ group_id ] . group_id
! = DM_STATS_GROUP_NOT_PRESENT )
nr_groups + + ;
return nr_groups ;
}
2015-08-05 12:40:00 +03:00
/**
2016-06-19 15:30:46 +03:00
* Test whether region_id is present in this set of stats data .
2015-08-05 12:40:00 +03:00
*/
int dm_stats_region_present ( const struct dm_stats * dms , uint64_t region_id )
{
if ( ! dms - > regions )
2015-09-06 01:56:30 +03:00
return_0 ;
2015-08-05 12:40:00 +03:00
if ( region_id > dms - > max_region )
2016-02-29 20:52:29 +03:00
return 0 ;
2015-08-05 12:40:00 +03:00
return _stats_region_present ( & dms - > regions [ region_id ] ) ;
}
static int _dm_stats_populate_region ( struct dm_stats * dms , uint64_t region_id ,
const char * resp )
{
struct dm_stats_region * region = & dms - > regions [ region_id ] ;
if ( ! _stats_bound ( dms ) )
return_0 ;
2016-11-17 14:39:43 +03:00
if ( ! region ) {
log_error ( " Cannot populate empty handle before dm_stats_list(). " ) ;
return 0 ;
}
if ( ! _stats_parse_region ( dms , resp , region , region - > timescale ) ) {
2015-08-05 12:40:00 +03:00
log_error ( " Could not parse @stats_print message response. " ) ;
return 0 ;
}
region - > region_id = region_id ;
return 1 ;
}
int dm_stats_populate ( struct dm_stats * dms , const char * program_id ,
uint64_t region_id )
{
int all_regions = ( region_id = = DM_STATS_REGIONS_ALL ) ;
2015-09-07 22:08:10 +03:00
struct dm_task * dmt = NULL ; /* @stats_print task */
2016-06-19 15:30:46 +03:00
uint64_t saved_flags ; /* saved walk flags */
2015-09-07 22:08:10 +03:00
const char * resp ;
2015-08-05 12:40:00 +03:00
2016-06-19 15:30:46 +03:00
/*
* We are about do destroy and re - create the region table , so it
* is safe to use the cursor embedded in the stats handle : just
* save a copy of the current walk_flags to restore later .
*/
saved_flags = dms - > walk_flags ;
2015-08-05 12:40:00 +03:00
if ( ! _stats_bound ( dms ) )
return_0 ;
2016-06-19 15:30:46 +03:00
if ( ( ! all_regions ) & & ( region_id & DM_STATS_WALK_GROUP ) ) {
log_error ( " Invalid region_id for dm_stats_populate: "
" DM_STATS_WALK_GROUP " ) ;
return 0 ;
}
2015-08-05 12:40:00 +03:00
/* allow zero-length program_id for populate */
if ( ! program_id )
program_id = dms - > program_id ;
if ( all_regions & & ! dm_stats_list ( dms , program_id ) ) {
log_error ( " Could not parse @stats_list response. " ) ;
2015-09-06 01:56:30 +03:00
goto bad ;
2016-06-17 14:17:33 +03:00
} else if ( ! _stats_set_name_cache ( dms ) ) {
goto_bad ;
2015-08-05 12:40:00 +03:00
}
/* successful list but no regions registered */
if ( ! dms - > nr_regions )
2016-07-05 20:34:14 +03:00
return 0 ;
2015-08-05 12:40:00 +03:00
2016-06-19 15:30:46 +03:00
dms - > walk_flags = DM_STATS_WALK_REGION ;
2015-08-05 12:40:00 +03:00
dm_stats_walk_start ( dms ) ;
do {
region_id = ( all_regions )
? dm_stats_get_current_region ( dms ) : region_id ;
/* obtain all lines and clear counter values */
if ( ! ( dmt = _stats_print_region ( dms , region_id , 0 , 0 , 1 ) ) )
2015-09-06 01:56:30 +03:00
goto_bad ;
2015-08-05 12:40:00 +03:00
resp = dm_task_get_message_response ( dmt ) ;
if ( ! _dm_stats_populate_region ( dms , region_id , resp ) ) {
dm_task_destroy ( dmt ) ;
2015-09-06 01:56:30 +03:00
goto_bad ;
2015-08-05 12:40:00 +03:00
}
dm_task_destroy ( dmt ) ;
2016-06-19 15:30:46 +03:00
dm_stats_walk_next ( dms ) ;
2015-08-05 12:40:00 +03:00
} while ( all_regions & & ! dm_stats_walk_end ( dms ) ) ;
2016-06-19 15:30:46 +03:00
dms - > walk_flags = saved_flags ;
2015-08-05 12:40:00 +03:00
return 1 ;
2015-09-06 01:56:30 +03:00
bad :
2016-06-19 15:30:46 +03:00
dms - > walk_flags = saved_flags ;
2015-08-05 12:40:00 +03:00
_stats_regions_destroy ( dms ) ;
dms - > regions = NULL ;
return 0 ;
}
/**
* destroy a dm_stats object and all associated regions and counter sets .
*/
void dm_stats_destroy ( struct dm_stats * dms )
{
_stats_regions_destroy ( dms ) ;
2016-02-29 20:52:29 +03:00
_stats_groups_destroy ( dms ) ;
2015-08-05 12:40:00 +03:00
_stats_clear_binding ( dms ) ;
dm_pool_destroy ( dms - > mem ) ;
2015-08-19 22:39:10 +03:00
dm_pool_destroy ( dms - > hist_mem ) ;
2016-02-29 20:52:29 +03:00
dm_pool_destroy ( dms - > group_mem ) ;
2015-08-05 12:40:00 +03:00
dm_free ( dms - > program_id ) ;
2016-06-17 14:17:33 +03:00
dm_free ( ( char * ) dms - > name ) ;
2015-08-05 12:40:00 +03:00
dm_free ( dms ) ;
}
2016-06-19 15:30:46 +03:00
/*
* Walk each area that is a member of region_id rid .
* i is a variable of type int that holds the current area_id .
*/
# define _foreach_region_area(dms, rid, i) \
for ( ( i ) = 0 ; ( i ) < _nr_areas_region ( & dms - > regions [ ( rid ) ] ) ; ( i ) + + ) \
/*
* Walk each region that is a member of group_id gid .
* i is a variable of type int that holds the current region_id .
*/
# define _foreach_group_region(dms, gid, i) \
for ( ( i ) = dm_bit_get_first ( ( dms ) - > groups [ ( gid ) ] . regions ) ; \
( i ) ! = DM_STATS_GROUP_NOT_PRESENT ; \
( i ) = dm_bit_get_next ( ( dms ) - > groups [ ( gid ) ] . regions , ( i ) ) ) \
2016-02-29 20:52:29 +03:00
/*
* Walk each region that is a member of group_id gid visiting each
* area within the region .
2016-06-19 15:30:46 +03:00
* i is a variable of type int that holds the current region_id .
* j is a variable of type int variable that holds the current area_id .
2016-02-29 20:52:29 +03:00
*/
2016-06-19 15:30:46 +03:00
# define _foreach_group_area(dms, gid, i, j) \
_foreach_group_region ( dms , gid , i ) \
_foreach_region_area ( dms , i , j )
2016-02-29 20:52:29 +03:00
2016-02-29 20:33:16 +03:00
static uint64_t _stats_get_counter ( const struct dm_stats * dms ,
const struct dm_stats_counters * area ,
dm_stats_counter_t counter )
{
switch ( counter ) {
case DM_STATS_READS_COUNT :
return area - > reads ;
case DM_STATS_READS_MERGED_COUNT :
return area - > reads_merged ;
case DM_STATS_READ_SECTORS_COUNT :
return area - > read_sectors ;
case DM_STATS_READ_NSECS :
return area - > read_nsecs ;
case DM_STATS_WRITES_COUNT :
return area - > writes ;
case DM_STATS_WRITES_MERGED_COUNT :
return area - > writes_merged ;
case DM_STATS_WRITE_SECTORS_COUNT :
return area - > write_sectors ;
case DM_STATS_WRITE_NSECS :
return area - > write_nsecs ;
case DM_STATS_IO_IN_PROGRESS_COUNT :
return area - > io_in_progress ;
case DM_STATS_IO_NSECS :
return area - > io_nsecs ;
case DM_STATS_WEIGHTED_IO_NSECS :
return area - > weighted_io_nsecs ;
case DM_STATS_TOTAL_READ_NSECS :
return area - > total_read_nsecs ;
case DM_STATS_TOTAL_WRITE_NSECS :
return area - > total_write_nsecs ;
case DM_STATS_NR_COUNTERS :
default :
log_error ( " Attempt to read invalid counter: %d " , counter ) ;
}
return 0 ;
}
uint64_t dm_stats_get_counter ( const struct dm_stats * dms ,
dm_stats_counter_t counter ,
uint64_t region_id , uint64_t area_id )
{
2016-06-19 15:30:46 +03:00
uint64_t i , j , sum = 0 ; /* aggregation */
int sum_regions = 0 ;
2016-02-29 20:33:16 +03:00
struct dm_stats_region * region ;
struct dm_stats_counters * area ;
region_id = ( region_id = = DM_STATS_REGION_CURRENT )
? dms - > cur_region : region_id ;
area_id = ( area_id = = DM_STATS_REGION_CURRENT )
? dms - > cur_area : area_id ;
2016-06-19 15:30:46 +03:00
sum_regions = ! ! ( region_id & DM_STATS_WALK_GROUP ) ;
if ( region_id = = DM_STATS_WALK_GROUP )
/* group walk using the cursor */
region_id = dms - > cur_group ;
else if ( region_id & DM_STATS_WALK_GROUP )
/* group walk using immediate group_id */
region_id & = ~ DM_STATS_WALK_GROUP ;
2016-02-29 20:33:16 +03:00
region = & dms - > regions [ region_id ] ;
2016-06-19 15:30:46 +03:00
/*
* All statistics aggregation takes place here : aggregate metrics
* are calculated as normal using the aggregated counter values
* returned for the region or group specified .
*/
2016-02-29 20:33:16 +03:00
2016-06-19 15:30:46 +03:00
if ( _stats_region_is_grouped ( dms , region_id ) & & ( sum_regions ) ) {
/* group */
if ( area_id & DM_STATS_WALK_GROUP )
_foreach_group_area ( dms , region - > group_id , i , j ) {
area = & dms - > regions [ i ] . counters [ j ] ;
sum + = _stats_get_counter ( dms , area , counter ) ;
}
else
_foreach_group_region ( dms , region - > group_id , i ) {
area = & dms - > regions [ i ] . counters [ area_id ] ;
sum + = _stats_get_counter ( dms , area , counter ) ;
}
} else if ( area_id = = DM_STATS_WALK_REGION ) {
/* aggregate region */
_foreach_region_area ( dms , region_id , j ) {
area = & dms - > regions [ region_id ] . counters [ j ] ;
sum + = _stats_get_counter ( dms , area , counter ) ;
}
} else {
/* plain region / area */
area = & region - > counters [ area_id ] ;
sum = _stats_get_counter ( dms , area , counter ) ;
}
return sum ;
2016-02-29 20:33:16 +03:00
}
2016-06-19 15:30:46 +03:00
/*
2016-02-29 20:33:16 +03:00
* Methods for accessing named counter fields . All methods share the
2015-08-05 12:40:00 +03:00
* following naming scheme and prototype :
*
2016-02-29 20:33:16 +03:00
* uint64_t dm_stats_get_COUNTER ( const struct dm_stats * , uint64_t , uint64_t )
2015-08-05 12:40:00 +03:00
*
* Where the two integer arguments are the region_id and area_id
* respectively .
2016-02-29 20:33:16 +03:00
*
* name is the name of the counter ( lower case )
* counter is the part of the enum name following DM_STATS_ ( upper case )
2015-08-05 12:40:00 +03:00
*/
2016-02-29 20:33:16 +03:00
# define MK_STATS_GET_COUNTER_FN(name, counter) \
uint64_t dm_stats_get_ # # name ( const struct dm_stats * dms , \
uint64_t region_id , uint64_t area_id ) \
2015-08-05 12:40:00 +03:00
{ \
2016-02-29 20:33:16 +03:00
return dm_stats_get_counter ( dms , DM_STATS_ # # counter , \
region_id , area_id ) ; \
}
MK_STATS_GET_COUNTER_FN ( reads , READS_COUNT )
MK_STATS_GET_COUNTER_FN ( reads_merged , READS_MERGED_COUNT )
MK_STATS_GET_COUNTER_FN ( read_sectors , READ_SECTORS_COUNT )
MK_STATS_GET_COUNTER_FN ( read_nsecs , READ_NSECS )
MK_STATS_GET_COUNTER_FN ( writes , WRITES_COUNT )
MK_STATS_GET_COUNTER_FN ( writes_merged , WRITES_MERGED_COUNT )
MK_STATS_GET_COUNTER_FN ( write_sectors , WRITE_SECTORS_COUNT )
MK_STATS_GET_COUNTER_FN ( write_nsecs , WRITE_NSECS )
MK_STATS_GET_COUNTER_FN ( io_in_progress , IO_IN_PROGRESS_COUNT )
MK_STATS_GET_COUNTER_FN ( io_nsecs , IO_NSECS )
MK_STATS_GET_COUNTER_FN ( weighted_io_nsecs , WEIGHTED_IO_NSECS )
MK_STATS_GET_COUNTER_FN ( total_read_nsecs , TOTAL_READ_NSECS )
MK_STATS_GET_COUNTER_FN ( total_write_nsecs , TOTAL_WRITE_NSECS )
2015-08-05 12:40:00 +03:00
# undef MK_STATS_GET_COUNTER_FN
2016-02-29 20:33:16 +03:00
/*
* Floating point stats metric functions
*
* Called from dm_stats_get_metric ( ) to calculate the value of
* the requested metric .
*
* int _metric_name ( const struct dm_stats * dms ,
* struct dm_stats_counters * c ,
* double * value ) ;
*
* Calculate a metric value from the counter data for the given
* identifiers and store it in the memory pointed to by value ,
* applying group or region aggregation if enabled .
*
* Return one on success or zero on failure .
*
* To add a new metric :
*
* o Add a new name to the dm_stats_metric_t enum .
* o Create a _metric_fn ( ) to calculate the new metric .
* o Add _metric_fn to the _metrics function table
* ( entries in enum order ) .
* o Do not add a new named public function for the metric -
* users of new metrics are encouraged to convert to the enum
* based metric interface .
*
*/
2015-08-05 12:40:00 +03:00
2016-02-29 20:33:16 +03:00
static int _rd_merges_per_sec ( const struct dm_stats * dms , double * rrqm ,
uint64_t region_id , uint64_t area_id )
{
double mrgs ;
mrgs = ( double ) dm_stats_get_counter ( dms , DM_STATS_READS_MERGED_COUNT ,
region_id , area_id ) ;
2015-08-05 12:40:00 +03:00
2016-02-29 20:33:16 +03:00
* rrqm = mrgs / ( double ) dms - > interval_ns ;
2015-08-05 12:40:00 +03:00
return 1 ;
}
2016-02-29 20:33:16 +03:00
static int _wr_merges_per_sec ( const struct dm_stats * dms , double * wrqm ,
uint64_t region_id , uint64_t area_id )
2015-08-05 12:40:00 +03:00
{
2016-02-29 20:33:16 +03:00
double mrgs ;
mrgs = ( double ) dm_stats_get_counter ( dms , DM_STATS_WRITES_MERGED_COUNT ,
region_id , area_id ) ;
2015-08-05 12:40:00 +03:00
2016-02-29 20:33:16 +03:00
* wrqm = mrgs / ( double ) dms - > interval_ns ;
2015-08-05 12:40:00 +03:00
return 1 ;
}
2016-02-29 20:33:16 +03:00
static int _reads_per_sec ( const struct dm_stats * dms , double * rd_s ,
uint64_t region_id , uint64_t area_id )
2015-08-05 12:40:00 +03:00
{
2016-02-29 20:33:16 +03:00
double reads ;
reads = ( double ) dm_stats_get_counter ( dms , DM_STATS_READS_COUNT ,
region_id , area_id ) ;
2015-08-05 12:40:00 +03:00
2016-02-29 20:33:16 +03:00
* rd_s = ( reads * NSEC_PER_SEC ) / ( double ) dms - > interval_ns ;
2015-08-05 12:40:00 +03:00
return 1 ;
}
2016-02-29 20:33:16 +03:00
static int _writes_per_sec ( const struct dm_stats * dms , double * wr_s ,
uint64_t region_id , uint64_t area_id )
2015-08-05 12:40:00 +03:00
{
2016-02-29 20:33:16 +03:00
double writes ;
writes = ( double ) dm_stats_get_counter ( dms , DM_STATS_WRITES_COUNT ,
region_id , area_id ) ;
2015-08-05 12:40:00 +03:00
2016-02-29 20:33:16 +03:00
* wr_s = ( writes * NSEC_PER_SEC ) / ( double ) dms - > interval_ns ;
2015-08-05 12:40:00 +03:00
2016-02-29 20:33:16 +03:00
return 1 ;
}
2015-08-05 12:40:00 +03:00
2016-02-29 20:33:16 +03:00
static int _read_sectors_per_sec ( const struct dm_stats * dms , double * rsec_s ,
uint64_t region_id , uint64_t area_id )
{
double sect ;
sect = ( double ) dm_stats_get_counter ( dms , DM_STATS_READ_SECTORS_COUNT ,
region_id , area_id ) ;
* rsec_s = ( sect * ( double ) NSEC_PER_SEC ) / ( double ) dms - > interval_ns ;
2015-08-05 12:40:00 +03:00
return 1 ;
}
2016-02-29 20:33:16 +03:00
static int _write_sectors_per_sec ( const struct dm_stats * dms , double * wsec_s ,
uint64_t region_id , uint64_t area_id )
2015-08-05 12:40:00 +03:00
{
2016-02-29 20:33:16 +03:00
double sect ;
sect = ( double ) dm_stats_get_counter ( dms , DM_STATS_WRITE_SECTORS_COUNT ,
region_id , area_id ) ;
2015-08-05 12:40:00 +03:00
2016-02-29 20:33:16 +03:00
* wsec_s = ( sect * ( double ) NSEC_PER_SEC ) / ( double ) dms - > interval_ns ;
2015-08-05 12:40:00 +03:00
2016-02-29 20:33:16 +03:00
return 1 ;
}
static int _average_request_size ( const struct dm_stats * dms , double * arqsz ,
uint64_t region_id , uint64_t area_id )
{
double ios , sectors ;
ios = ( double ) ( dm_stats_get_counter ( dms , DM_STATS_READS_COUNT ,
region_id , area_id )
+ dm_stats_get_counter ( dms , DM_STATS_WRITES_COUNT ,
region_id , area_id ) ) ;
sectors = ( double ) ( dm_stats_get_counter ( dms , DM_STATS_READ_SECTORS_COUNT ,
region_id , area_id )
+ dm_stats_get_counter ( dms , DM_STATS_WRITE_SECTORS_COUNT ,
region_id , area_id ) ) ;
2015-08-05 12:40:00 +03:00
2016-02-29 20:33:16 +03:00
if ( ios > 0.0 )
* arqsz = sectors / ios ;
else
* arqsz = 0.0 ;
2015-08-05 12:40:00 +03:00
return 1 ;
}
2016-02-29 20:33:16 +03:00
static int _average_queue_size ( const struct dm_stats * dms , double * qusz ,
uint64_t region_id , uint64_t area_id )
2015-08-05 12:40:00 +03:00
{
2016-02-29 20:33:16 +03:00
double io_ticks ;
io_ticks = ( double ) dm_stats_get_counter ( dms , DM_STATS_WEIGHTED_IO_NSECS ,
region_id , area_id ) ;
2015-08-05 12:40:00 +03:00
2016-02-29 20:33:16 +03:00
if ( io_ticks > 0.0 )
* qusz = io_ticks / ( double ) dms - > interval_ns ;
else
* qusz = 0.0 ;
2015-08-05 12:40:00 +03:00
return 1 ;
}
2016-02-29 20:33:16 +03:00
static int _average_wait_time ( const struct dm_stats * dms , double * await ,
uint64_t region_id , uint64_t area_id )
2015-08-05 12:40:00 +03:00
{
2016-02-29 20:33:16 +03:00
uint64_t io_ticks , nr_ios ;
2015-08-05 12:40:00 +03:00
2016-02-29 20:33:16 +03:00
io_ticks = dm_stats_get_counter ( dms , DM_STATS_READ_NSECS ,
region_id , area_id ) ;
io_ticks + = dm_stats_get_counter ( dms , DM_STATS_WRITE_NSECS ,
region_id , area_id ) ;
2015-08-05 12:40:00 +03:00
2016-02-29 20:33:16 +03:00
nr_ios = dm_stats_get_counter ( dms , DM_STATS_READS_COUNT ,
region_id , area_id ) ;
nr_ios + = dm_stats_get_counter ( dms , DM_STATS_WRITES_COUNT ,
region_id , area_id ) ;
2015-08-05 12:40:00 +03:00
2016-02-29 20:33:16 +03:00
if ( nr_ios > 0 )
* await = ( double ) io_ticks / ( double ) nr_ios ;
else
* await = 0.0 ;
2015-08-05 12:40:00 +03:00
return 1 ;
}
2016-02-29 20:33:16 +03:00
static int _average_rd_wait_time ( const struct dm_stats * dms , double * await ,
uint64_t region_id , uint64_t area_id )
2015-08-05 12:40:00 +03:00
{
2016-02-29 20:33:16 +03:00
uint64_t rd_io_ticks , nr_rd_ios ;
2015-08-05 12:40:00 +03:00
2016-02-29 20:33:16 +03:00
rd_io_ticks = dm_stats_get_counter ( dms , DM_STATS_READ_NSECS ,
region_id , area_id ) ;
nr_rd_ios = dm_stats_get_counter ( dms , DM_STATS_READS_COUNT ,
region_id , area_id ) ;
2015-08-05 12:40:00 +03:00
2016-07-06 11:20:05 +03:00
/*
* If rd_io_ticks is > 0 this should imply that nr_rd_ios is
* also > 0 ( unless a kernel bug exists ) . Test for both here
* before using the IO count as a divisor ( Coverity ) .
*/
if ( rd_io_ticks > 0 & & nr_rd_ios > 0 )
2016-02-29 20:33:16 +03:00
* await = ( double ) rd_io_ticks / ( double ) nr_rd_ios ;
else
* await = 0.0 ;
2015-08-05 12:40:00 +03:00
return 1 ;
}
2016-02-29 20:33:16 +03:00
static int _average_wr_wait_time ( const struct dm_stats * dms , double * await ,
uint64_t region_id , uint64_t area_id )
2015-08-05 12:40:00 +03:00
{
2016-02-29 20:33:16 +03:00
uint64_t wr_io_ticks , nr_wr_ios ;
2015-08-05 12:40:00 +03:00
2016-02-29 20:33:16 +03:00
wr_io_ticks = dm_stats_get_counter ( dms , DM_STATS_WRITE_NSECS ,
region_id , area_id ) ;
nr_wr_ios = dm_stats_get_counter ( dms , DM_STATS_WRITES_COUNT ,
region_id , area_id ) ;
2015-08-05 12:40:00 +03:00
2016-07-06 11:20:05 +03:00
/*
* If wr_io_ticks is > 0 this should imply that nr_wr_ios is
* also > 0 ( unless a kernel bug exists ) . Test for both here
* before using the IO count as a divisor ( Coverity ) .
*/
if ( wr_io_ticks > 0 & & nr_wr_ios > 0 )
2016-02-29 20:33:16 +03:00
* await = ( double ) wr_io_ticks / ( double ) nr_wr_ios ;
else
* await = 0.0 ;
2015-08-05 12:40:00 +03:00
return 1 ;
}
2016-02-29 20:33:16 +03:00
static int _throughput ( const struct dm_stats * dms , double * tput ,
uint64_t region_id , uint64_t area_id )
2015-08-05 12:40:00 +03:00
{
2016-02-29 20:33:16 +03:00
uint64_t nr_ios ;
2015-08-05 12:40:00 +03:00
2016-02-29 20:33:16 +03:00
nr_ios = dm_stats_get_counter ( dms , DM_STATS_READS_COUNT ,
region_id , area_id ) ;
nr_ios + = dm_stats_get_counter ( dms , DM_STATS_WRITES_COUNT ,
region_id , area_id ) ;
2015-08-05 12:40:00 +03:00
2016-02-29 20:33:16 +03:00
* tput = ( ( double ) NSEC_PER_SEC * ( double ) nr_ios )
/ ( double ) ( dms - > interval_ns ) ;
2015-08-05 12:40:00 +03:00
return 1 ;
}
2016-02-29 20:33:16 +03:00
static int _utilization ( const struct dm_stats * dms , double * util ,
uint64_t region_id , uint64_t area_id )
2015-08-05 12:40:00 +03:00
{
2016-02-29 20:33:16 +03:00
uint64_t io_nsecs , interval_ns = dms - > interval_ns ;
2015-08-05 12:40:00 +03:00
2016-02-29 20:33:16 +03:00
/**
* If io_nsec > interval_ns there is something wrong with the clock
* for the last interval ; do not allow a value > 100 % utilization
* to be passed to a dm_make_percent ( ) call . We expect to see these
* at startup if counters have not been cleared before the first read .
2016-07-06 11:12:51 +03:00
*
* A zero interval_ns is also an error since metrics cannot be
* calculated without a defined interval - return zero and emit a
* backtrace in this case .
2016-02-29 20:33:16 +03:00
*/
io_nsecs = dm_stats_get_counter ( dms , DM_STATS_IO_NSECS ,
region_id , area_id ) ;
2015-08-05 12:40:00 +03:00
2016-07-06 11:12:51 +03:00
if ( ! interval_ns ) {
* util = 0.0 ;
return_0 ;
}
2016-02-29 20:33:16 +03:00
io_nsecs = ( ( io_nsecs < interval_ns ) ? io_nsecs : interval_ns ) ;
2015-08-05 12:40:00 +03:00
2016-02-29 20:33:16 +03:00
* util = ( double ) io_nsecs / ( double ) interval_ns ;
2015-08-05 12:40:00 +03:00
return 1 ;
}
2016-02-29 20:33:16 +03:00
static int _service_time ( const struct dm_stats * dms , double * svctm ,
uint64_t region_id , uint64_t area_id )
2015-08-05 12:40:00 +03:00
{
2016-02-29 20:33:16 +03:00
double tput , util ;
2015-08-05 12:40:00 +03:00
2016-02-29 20:33:16 +03:00
if ( ! _throughput ( dms , & tput , region_id , area_id ) )
return 0 ;
2015-08-05 12:40:00 +03:00
2016-02-29 20:33:16 +03:00
if ( ! _utilization ( dms , & util , region_id , area_id ) )
return 0 ;
util * = 100 ;
2015-08-05 12:40:00 +03:00
/* avoid NAN with zero counter values */
if ( ( uint64_t ) tput = = 0 | | ( uint64_t ) util = = 0 ) {
* svctm = 0.0 ;
return 1 ;
}
2016-02-29 20:33:16 +03:00
2015-08-05 12:40:00 +03:00
* svctm = ( ( double ) NSEC_PER_SEC * dm_percent_to_float ( util ) )
/ ( 100.0 * tput ) ;
2016-02-29 20:33:16 +03:00
2015-08-05 12:40:00 +03:00
return 1 ;
}
2016-02-29 20:33:16 +03:00
/*
* Table in enum order :
* DM_STATS_RD_MERGES_PER_SEC ,
* DM_STATS_WR_MERGES_PER_SEC ,
* DM_STATS_READS_PER_SEC ,
* DM_STATS_WRITES_PER_SEC ,
* DM_STATS_READ_SECTORS_PER_SEC ,
* DM_STATS_WRITE_SECTORS_PER_SEC ,
* DM_STATS_AVERAGE_REQUEST_SIZE ,
* DM_STATS_AVERAGE_QUEUE_SIZE ,
* DM_STATS_AVERAGE_WAIT_TIME ,
* DM_STATS_AVERAGE_RD_WAIT_TIME ,
* DM_STATS_AVERAGE_WR_WAIT_TIME
* DM_STATS_SERVICE_TIME ,
* DM_STATS_THROUGHPUT ,
* DM_STATS_UTILIZATION
*
*/
typedef int ( * _metric_fn_t ) ( const struct dm_stats * , double * ,
uint64_t , uint64_t ) ;
_metric_fn_t _metrics [ DM_STATS_NR_METRICS ] = {
_rd_merges_per_sec ,
_wr_merges_per_sec ,
_reads_per_sec ,
_writes_per_sec ,
_read_sectors_per_sec ,
_write_sectors_per_sec ,
_average_request_size ,
_average_queue_size ,
_average_wait_time ,
_average_rd_wait_time ,
_average_wr_wait_time ,
_service_time ,
_throughput ,
_utilization
} ;
2015-08-05 12:40:00 +03:00
2016-02-29 20:33:16 +03:00
int dm_stats_get_metric ( const struct dm_stats * dms , int metric ,
uint64_t region_id , uint64_t area_id , double * value )
{
2015-08-05 12:40:00 +03:00
if ( ! dms - > interval_ns )
return_0 ;
2016-06-19 15:30:46 +03:00
/*
* Decode DM_STATS_ { REGION , AREA } _CURRENT here ; counters will then
* be returned for the actual current region and area .
*
* DM_STATS_WALK_GROUP is passed through to the counter methods -
* aggregates for the group are returned and used to calculate
* the metric for the group totals .
*/
2016-02-29 20:52:29 +03:00
region_id = ( region_id = = DM_STATS_REGION_CURRENT )
? dms - > cur_region : region_id ;
area_id = ( area_id = = DM_STATS_REGION_CURRENT )
? dms - > cur_area : area_id ;
2016-02-29 20:33:16 +03:00
if ( metric < 0 | | metric > = DM_STATS_NR_METRICS ) {
log_error ( " Attempt to read invalid metric: %d " , metric ) ;
return 0 ;
}
2015-08-05 12:40:00 +03:00
2016-02-29 20:33:16 +03:00
return _metrics [ metric ] ( dms , value , region_id , area_id ) ;
2015-08-05 12:40:00 +03:00
}
2016-02-29 20:33:16 +03:00
/**
* Methods for accessing stats metrics . All methods share the
* following naming scheme and prototype :
*
* uint64_t dm_stats_get_metric ( struct dm_stats * ,
* int , int ,
* uint64_t , uint64_t ,
* double * v )
*
* Where the two integer arguments are the region_id and area_id
* respectively .
*
* name is the name of the metric ( lower case )
* metric is the part of the enum name following DM_STATS_ ( upper case )
*/
# define MK_STATS_GET_METRIC_FN(name, metric, meta) \
int dm_stats_get_ # # name ( const struct dm_stats * dms , double * meta , \
uint64_t region_id , uint64_t area_id ) \
{ \
return dm_stats_get_metric ( dms , DM_STATS_ # # metric , \
region_id , area_id , meta ) ; \
}
MK_STATS_GET_METRIC_FN ( rd_merges_per_sec , RD_MERGES_PER_SEC , rrqm )
MK_STATS_GET_METRIC_FN ( wr_merges_per_sec , WR_MERGES_PER_SEC , wrqm )
MK_STATS_GET_METRIC_FN ( reads_per_sec , READS_PER_SEC , rd_s )
MK_STATS_GET_METRIC_FN ( writes_per_sec , WRITES_PER_SEC , wr_s )
MK_STATS_GET_METRIC_FN ( read_sectors_per_sec , READ_SECTORS_PER_SEC , rsec_s )
MK_STATS_GET_METRIC_FN ( write_sectors_per_sec , WRITE_SECTORS_PER_SEC , wsec_s )
MK_STATS_GET_METRIC_FN ( average_request_size , AVERAGE_REQUEST_SIZE , arqsz )
MK_STATS_GET_METRIC_FN ( average_queue_size , AVERAGE_QUEUE_SIZE , qusz )
MK_STATS_GET_METRIC_FN ( average_wait_time , AVERAGE_WAIT_TIME , await )
MK_STATS_GET_METRIC_FN ( average_rd_wait_time , AVERAGE_RD_WAIT_TIME , await )
MK_STATS_GET_METRIC_FN ( average_wr_wait_time , AVERAGE_WR_WAIT_TIME , await )
MK_STATS_GET_METRIC_FN ( service_time , SERVICE_TIME , svctm )
MK_STATS_GET_METRIC_FN ( throughput , THROUGHPUT , tput )
/*
* Utilization is an exception since it used the dm_percent_t type in the
* original named function based interface : preserve this behaviour for
* backwards compatibility with existing users .
*
* The same metric may be accessed as a double via the enum based metric
* interface .
*/
2015-08-05 12:40:00 +03:00
int dm_stats_get_utilization ( const struct dm_stats * dms , dm_percent_t * util ,
uint64_t region_id , uint64_t area_id )
{
2016-02-29 20:33:16 +03:00
double _util ;
2015-08-05 12:40:00 +03:00
2016-02-29 20:33:16 +03:00
if ( ! dm_stats_get_metric ( dms , DM_STATS_UTILIZATION ,
region_id , area_id , & _util ) )
2015-08-05 12:40:00 +03:00
return_0 ;
2016-02-29 20:33:16 +03:00
/* scale up utilization value in the range [0.00..1.00] */
* util = dm_make_percent ( DM_PERCENT_1 * _util , DM_PERCENT_1 ) ;
2015-08-05 12:40:00 +03:00
return 1 ;
}
void dm_stats_set_sampling_interval_ms ( struct dm_stats * dms , uint64_t interval_ms )
{
/* All times use nsecs internally. */
dms - > interval_ns = interval_ms * NSEC_PER_MSEC ;
}
void dm_stats_set_sampling_interval_ns ( struct dm_stats * dms , uint64_t interval_ns )
{
dms - > interval_ns = interval_ns ;
}
uint64_t dm_stats_get_sampling_interval_ms ( const struct dm_stats * dms )
{
/* All times use nsecs internally. */
return ( dms - > interval_ns / NSEC_PER_MSEC ) ;
}
uint64_t dm_stats_get_sampling_interval_ns ( const struct dm_stats * dms )
{
/* All times use nsecs internally. */
return ( dms - > interval_ns ) ;
}
int dm_stats_set_program_id ( struct dm_stats * dms , int allow_empty ,
const char * program_id )
{
if ( ! allow_empty & & ( ! program_id | | ! strlen ( program_id ) ) ) {
log_error ( " Empty program_id not permitted without "
" allow_empty=1 " ) ;
return 0 ;
}
if ( ! program_id )
program_id = " " ;
if ( dms - > program_id )
dm_free ( dms - > program_id ) ;
if ( ! ( dms - > program_id = dm_strdup ( program_id ) ) )
return_0 ;
return 1 ;
}
uint64_t dm_stats_get_current_region ( const struct dm_stats * dms )
{
return dms - > cur_region ;
}
uint64_t dm_stats_get_current_area ( const struct dm_stats * dms )
{
2016-06-19 15:30:46 +03:00
return dms - > cur_area & ~ DM_STATS_WALK_ALL ;
2015-08-05 12:40:00 +03:00
}
2015-08-17 17:23:27 +03:00
int dm_stats_get_region_start ( const struct dm_stats * dms , uint64_t * start ,
2015-08-05 12:40:00 +03:00
uint64_t region_id )
{
if ( ! dms | | ! dms - > regions )
return_0 ;
2016-06-19 15:30:46 +03:00
/* start is unchanged when aggregating areas */
if ( region_id & DM_STATS_WALK_REGION )
region_id & = ~ DM_STATS_WALK_REGION ;
/* use start of first region as group start */
if ( region_id & DM_STATS_WALK_GROUP ) {
if ( region_id = = DM_STATS_WALK_GROUP )
region_id = dms - > cur_group ;
else
region_id & = ~ DM_STATS_WALK_GROUP ;
}
2015-08-05 12:40:00 +03:00
* start = dms - > regions [ region_id ] . start ;
return 1 ;
}
2015-08-17 17:23:27 +03:00
int dm_stats_get_region_len ( const struct dm_stats * dms , uint64_t * len ,
2015-08-05 12:40:00 +03:00
uint64_t region_id )
{
2016-06-19 15:30:46 +03:00
uint64_t i ;
2015-08-05 12:40:00 +03:00
if ( ! dms | | ! dms - > regions )
return_0 ;
2016-06-19 15:30:46 +03:00
* len = 0 ;
/* length is unchanged when aggregating areas */
if ( region_id & DM_STATS_WALK_REGION )
region_id & = ~ DM_STATS_WALK_REGION ;
if ( region_id & DM_STATS_WALK_GROUP ) {
/* decode region / group ID */
if ( region_id = = DM_STATS_WALK_GROUP )
region_id = dms - > cur_group ;
else
region_id & = ~ DM_STATS_WALK_GROUP ;
/* use sum of region sizes as group size */
if ( _stats_region_is_grouped ( dms , region_id ) )
_foreach_group_region ( dms , dms - > cur_group , i )
* len + = dms - > regions [ i ] . len ;
else {
log_error ( " Group ID " FMTu64 " does not exist " ,
region_id ) ;
return 0 ;
}
} else
* len = dms - > regions [ region_id ] . len ;
2015-08-05 12:40:00 +03:00
return 1 ;
}
2015-08-17 17:23:27 +03:00
int dm_stats_get_region_area_len ( const struct dm_stats * dms , uint64_t * len ,
uint64_t region_id )
2015-08-05 12:40:00 +03:00
{
if ( ! dms | | ! dms - > regions )
return_0 ;
2016-06-19 15:30:46 +03:00
/* groups are not subdivided - area size equals group size */
if ( region_id & ( DM_STATS_WALK_GROUP | DM_STATS_WALK_REGION ) )
/* get_region_len will decode region_id */
return dm_stats_get_region_len ( dms , len , region_id ) ;
2015-08-17 17:23:27 +03:00
* len = dms - > regions [ region_id ] . step ;
2015-08-05 12:40:00 +03:00
return 1 ;
}
2015-08-17 17:23:27 +03:00
int dm_stats_get_current_region_start ( const struct dm_stats * dms ,
uint64_t * start )
2015-08-05 12:40:00 +03:00
{
return dm_stats_get_region_start ( dms , start , dms - > cur_region ) ;
}
2015-08-17 17:23:27 +03:00
int dm_stats_get_current_region_len ( const struct dm_stats * dms ,
uint64_t * len )
2015-08-05 12:40:00 +03:00
{
return dm_stats_get_region_len ( dms , len , dms - > cur_region ) ;
}
2015-08-17 17:23:27 +03:00
int dm_stats_get_current_region_area_len ( const struct dm_stats * dms ,
uint64_t * step )
2015-08-05 12:40:00 +03:00
{
return dm_stats_get_region_area_len ( dms , step , dms - > cur_region ) ;
}
2015-08-17 17:23:27 +03:00
int dm_stats_get_area_start ( const struct dm_stats * dms , uint64_t * start ,
uint64_t region_id , uint64_t area_id )
2015-08-05 12:40:00 +03:00
{
2015-08-17 20:25:22 +03:00
struct dm_stats_region * region ;
2015-08-05 12:40:00 +03:00
if ( ! dms | | ! dms - > regions )
return_0 ;
2016-06-19 15:30:46 +03:00
/* group or region area start equals region start */
if ( region_id & ( DM_STATS_WALK_GROUP | DM_STATS_WALK_REGION ) )
return dm_stats_get_region_start ( dms , start , region_id ) ;
2015-08-17 20:25:22 +03:00
region = & dms - > regions [ region_id ] ;
2015-08-14 22:28:06 +03:00
* start = region - > start + region - > step * area_id ;
2015-08-05 12:40:00 +03:00
return 1 ;
}
2015-08-17 17:23:27 +03:00
int dm_stats_get_area_offset ( const struct dm_stats * dms , uint64_t * offset ,
uint64_t region_id , uint64_t area_id )
2015-08-14 22:30:05 +03:00
{
if ( ! dms | | ! dms - > regions )
return_0 ;
2016-06-19 15:30:46 +03:00
/* no areas for groups or aggregate regions */
if ( region_id & ( DM_STATS_WALK_GROUP | DM_STATS_WALK_REGION ) )
* offset = 0 ;
else
* offset = dms - > regions [ region_id ] . step * area_id ;
2015-08-17 15:56:44 +03:00
return 1 ;
2015-08-14 22:30:05 +03:00
}
2015-08-17 17:23:27 +03:00
int dm_stats_get_current_area_start ( const struct dm_stats * dms ,
uint64_t * start )
2015-08-05 12:40:00 +03:00
{
return dm_stats_get_area_start ( dms , start ,
dms - > cur_region , dms - > cur_area ) ;
}
2015-08-17 17:23:27 +03:00
int dm_stats_get_current_area_offset ( const struct dm_stats * dms ,
2015-08-14 22:30:05 +03:00
uint64_t * offset )
{
return dm_stats_get_area_offset ( dms , offset ,
dms - > cur_region , dms - > cur_area ) ;
}
2015-08-17 17:23:27 +03:00
int dm_stats_get_current_area_len ( const struct dm_stats * dms ,
uint64_t * len )
2015-08-05 12:40:00 +03:00
{
return dm_stats_get_region_area_len ( dms , len , dms - > cur_region ) ;
}
const char * dm_stats_get_region_program_id ( const struct dm_stats * dms ,
uint64_t region_id )
{
2016-06-19 15:30:46 +03:00
const char * program_id = NULL ;
if ( region_id & DM_STATS_WALK_GROUP )
return dms - > program_id ;
if ( region_id & DM_STATS_WALK_REGION )
region_id & = ~ DM_STATS_WALK_REGION ;
program_id = dms - > regions [ region_id ] . program_id ;
2015-08-05 12:40:00 +03:00
return ( program_id ) ? program_id : " " ;
}
const char * dm_stats_get_region_aux_data ( const struct dm_stats * dms ,
uint64_t region_id )
{
2016-06-19 15:30:46 +03:00
const char * aux_data = NULL ;
if ( region_id & DM_STATS_WALK_GROUP )
return " " ;
if ( region_id & DM_STATS_WALK_REGION )
region_id & = ~ DM_STATS_WALK_REGION ;
aux_data = dms - > regions [ region_id ] . aux_data ;
2015-08-05 12:40:00 +03:00
return ( aux_data ) ? aux_data : " " ;
}
2016-02-29 20:52:29 +03:00
int dm_stats_set_alias ( struct dm_stats * dms , uint64_t group_id , const char * alias )
{
struct dm_stats_group * group = NULL ;
const char * old_alias = NULL ;
if ( ! dms - > regions | | ! dms - > groups | | ! alias )
return_0 ;
if ( ! _stats_region_is_grouped ( dms , group_id ) ) {
log_error ( " Cannot set alias for ungrouped region ID "
FMTu64 , group_id ) ;
return 0 ;
}
2016-06-19 15:30:46 +03:00
if ( group_id & DM_STATS_WALK_GROUP ) {
if ( group_id = = DM_STATS_WALK_GROUP )
group_id = dms - > cur_group ;
else
group_id & = ~ DM_STATS_WALK_GROUP ;
}
2016-02-29 20:52:29 +03:00
if ( group_id ! = dms - > regions [ group_id ] . group_id ) {
/* dm_stats_set_alias() must be called on the group ID. */
log_error ( " Cannot set alias for group member " FMTu64 " . " ,
group_id ) ;
return 0 ;
}
group = & dms - > groups [ group_id ] ;
old_alias = group - > alias ;
group - > alias = dm_strdup ( alias ) ;
if ( ! group - > alias ) {
log_error ( " Could not allocate memory for alias. " ) ;
goto bad ;
}
if ( ! _stats_set_aux ( dms , group_id , dms - > regions [ group_id ] . aux_data ) ) {
log_error ( " Could not set new aux_data " ) ;
goto bad ;
}
dm_free ( ( char * ) old_alias ) ;
return 1 ;
bad :
2016-07-08 12:51:19 +03:00
dm_free ( ( char * ) group - > alias ) ;
2016-02-29 20:52:29 +03:00
group - > alias = old_alias ;
return 0 ;
}
const char * dm_stats_get_alias ( const struct dm_stats * dms , uint64_t id )
{
const struct dm_stats_region * region ;
id = ( id = = DM_STATS_REGION_CURRENT ) ? dms - > cur_region : id ;
2016-06-19 15:30:46 +03:00
if ( id & DM_STATS_WALK_GROUP ) {
if ( id = = DM_STATS_WALK_GROUP )
id = dms - > cur_group ;
else
id & = ~ DM_STATS_WALK_GROUP ;
}
2016-02-29 20:52:29 +03:00
2016-06-19 15:30:46 +03:00
region = & dms - > regions [ id ] ;
2016-02-29 20:52:29 +03:00
if ( ! _stats_region_is_grouped ( dms , id )
| | ! dms - > groups [ region - > group_id ] . alias )
return dms - > name ;
return dms - > groups [ region - > group_id ] . alias ;
}
2015-08-05 12:40:00 +03:00
const char * dm_stats_get_current_region_program_id ( const struct dm_stats * dms )
{
return dm_stats_get_region_program_id ( dms , dms - > cur_region ) ;
}
const char * dm_stats_get_current_region_aux_data ( const struct dm_stats * dms )
{
return dm_stats_get_region_aux_data ( dms , dms - > cur_region ) ;
}
2015-08-17 20:08:18 +03:00
int dm_stats_get_region_precise_timestamps ( const struct dm_stats * dms ,
uint64_t region_id )
{
2016-06-19 15:30:46 +03:00
struct dm_stats_region * region ;
if ( region_id = = DM_STATS_REGION_CURRENT )
region_id = dms - > cur_region ;
if ( region_id = = DM_STATS_WALK_GROUP )
region_id = dms - > cur_group ;
else if ( region_id & DM_STATS_WALK_GROUP )
region_id & = ~ DM_STATS_WALK_GROUP ;
region = & dms - > regions [ region_id ] ;
2015-08-17 20:08:18 +03:00
return region - > timescale = = 1 ;
}
int dm_stats_get_current_region_precise_timestamps ( const struct dm_stats * dms )
{
2016-06-19 15:30:46 +03:00
return dm_stats_get_region_precise_timestamps ( dms ,
DM_STATS_REGION_CURRENT ) ;
2015-08-17 20:08:18 +03:00
}
2015-08-19 22:39:10 +03:00
/*
* Histogram access methods .
*/
2016-07-03 23:59:31 +03:00
static void _sum_histogram_bins ( const struct dm_stats * dms ,
struct dm_histogram * dmh_aggr ,
uint64_t region_id , uint64_t area_id )
{
struct dm_stats_region * region ;
struct dm_histogram_bin * bins ;
struct dm_histogram * dmh_cur ;
uint64_t bin ;
region = & dms - > regions [ region_id ] ;
dmh_cur = region - > counters [ area_id ] . histogram ;
bins = dmh_aggr - > bins ;
for ( bin = 0 ; bin < dmh_aggr - > nr_bins ; bin + + )
bins [ bin ] . count + = dmh_cur - > bins [ bin ] . count ;
}
/*
* Create an aggregate histogram for a sub - divided region or a group .
*/
static struct dm_histogram * _aggregate_histogram ( const struct dm_stats * dms ,
uint64_t region_id ,
uint64_t area_id )
{
struct dm_histogram * dmh_aggr , * dmh_cur , * * dmh_cachep ;
int bin , nr_bins , group = 1 ;
uint64_t group_id ;
size_t hist_size ;
if ( area_id = = DM_STATS_WALK_REGION ) {
/* region aggregation */
group = 0 ;
if ( ! _stats_region_present ( & dms - > regions [ region_id ] ) )
return_NULL ;
if ( ! dms - > regions [ region_id ] . bounds )
return_NULL ;
if ( ! dms - > regions [ region_id ] . counters )
return dms - > regions [ region_id ] . bounds ;
if ( dms - > regions [ region_id ] . histogram )
return dms - > regions [ region_id ] . histogram ;
dmh_cur = dms - > regions [ region_id ] . counters [ 0 ] . histogram ;
dmh_cachep = & dms - > regions [ region_id ] . histogram ;
nr_bins = dms - > regions [ region_id ] . bounds - > nr_bins ;
} else {
/* group aggregation */
group_id = region_id ;
area_id = DM_STATS_WALK_GROUP ;
if ( ! _stats_group_id_present ( dms , group_id ) )
return_NULL ;
if ( ! dms - > regions [ group_id ] . bounds )
return_NULL ;
if ( ! dms - > regions [ group_id ] . counters )
return dms - > regions [ group_id ] . bounds ;
if ( dms - > groups [ group_id ] . histogram )
return dms - > groups [ group_id ] . histogram ;
dmh_cur = dms - > regions [ group_id ] . counters [ 0 ] . histogram ;
dmh_cachep = & dms - > groups [ group_id ] . histogram ;
nr_bins = dms - > regions [ group_id ] . bounds - > nr_bins ;
}
hist_size = sizeof ( * dmh_aggr )
+ nr_bins * sizeof ( struct dm_histogram_bin ) ;
if ( ! ( dmh_aggr = dm_pool_zalloc ( dms - > hist_mem , hist_size ) ) ) {
log_error ( " Could not allocate group histogram " ) ;
return 0 ;
}
dmh_aggr - > nr_bins = dmh_cur - > nr_bins ;
dmh_aggr - > dms = dms ;
if ( ! group )
_foreach_region_area ( dms , region_id , area_id ) {
_sum_histogram_bins ( dms , dmh_aggr , region_id , area_id ) ;
}
else {
_foreach_group_area ( dms , group_id , region_id , area_id ) {
_sum_histogram_bins ( dms , dmh_aggr , region_id , area_id ) ;
}
}
for ( bin = 0 ; bin < nr_bins ; bin + + ) {
dmh_aggr - > sum + = dmh_aggr - > bins [ bin ] . count ;
dmh_aggr - > bins [ bin ] . upper = dmh_cur - > bins [ bin ] . upper ;
}
/* cache aggregate histogram for subsequent access */
* dmh_cachep = dmh_aggr ;
return dmh_aggr ;
}
2015-08-19 22:39:10 +03:00
struct dm_histogram * dm_stats_get_histogram ( const struct dm_stats * dms ,
uint64_t region_id ,
uint64_t area_id )
{
2016-07-03 23:59:31 +03:00
int aggr = 0 ;
2015-08-19 22:39:10 +03:00
2016-07-03 23:59:31 +03:00
if ( region_id = = DM_STATS_REGION_CURRENT ) {
region_id = dms - > cur_region ;
if ( region_id & DM_STATS_WALK_GROUP ) {
region_id = dms - > cur_group ;
aggr = 1 ;
}
} else if ( region_id & DM_STATS_WALK_GROUP ) {
region_id & = ~ DM_STATS_WALK_GROUP ;
aggr = 1 ;
2016-06-19 15:30:46 +03:00
}
2016-07-03 23:59:31 +03:00
area_id = ( area_id = = DM_STATS_AREA_CURRENT )
? dms - > cur_area : area_id ;
if ( area_id = = DM_STATS_WALK_REGION )
aggr = 1 ;
if ( aggr )
return _aggregate_histogram ( dms , region_id , area_id ) ;
2016-06-19 15:30:46 +03:00
if ( region_id & DM_STATS_WALK_REGION )
region_id & = ~ DM_STATS_WALK_REGION ;
2015-08-19 22:39:10 +03:00
if ( ! dms - > regions [ region_id ] . counters )
return dms - > regions [ region_id ] . bounds ;
return dms - > regions [ region_id ] . counters [ area_id ] . histogram ;
}
int dm_histogram_get_nr_bins ( const struct dm_histogram * dmh )
{
return dmh - > nr_bins ;
}
uint64_t dm_histogram_get_bin_lower ( const struct dm_histogram * dmh , int bin )
{
return ( ! bin ) ? 0 : dmh - > bins [ bin - 1 ] . upper ;
}
uint64_t dm_histogram_get_bin_upper ( const struct dm_histogram * dmh , int bin )
{
return dmh - > bins [ bin ] . upper ;
}
uint64_t dm_histogram_get_bin_width ( const struct dm_histogram * dmh , int bin )
{
uint64_t upper , lower ;
upper = dm_histogram_get_bin_upper ( dmh , bin ) ;
lower = dm_histogram_get_bin_lower ( dmh , bin ) ;
return ( upper - lower ) ;
}
uint64_t dm_histogram_get_bin_count ( const struct dm_histogram * dmh , int bin )
{
return dmh - > bins [ bin ] . count ;
}
uint64_t dm_histogram_get_sum ( const struct dm_histogram * dmh )
{
return dmh - > sum ;
}
dm_percent_t dm_histogram_get_bin_percent ( const struct dm_histogram * dmh ,
int bin )
{
uint64_t value = dm_histogram_get_bin_count ( dmh , bin ) ;
uint64_t width = dm_histogram_get_bin_width ( dmh , bin ) ;
uint64_t total = dm_histogram_get_sum ( dmh ) ;
double val = ( double ) value ;
if ( ! total | | ! value | | ! width )
return DM_PERCENT_0 ;
return dm_make_percent ( ( uint64_t ) val , total ) ;
}
/*
* Histogram string helper functions : used to construct histogram and
* bin boundary strings from numeric data .
*/
/*
* Allocate an unbound histogram object with nr_bins bins . Only used
* for histograms used to hold bounds values as arguments for calls to
* dm_stats_create_region ( ) .
*/
static struct dm_histogram * _alloc_dm_histogram ( int nr_bins )
{
/* Allocate space for dm_histogram + nr_entries. */
2015-11-09 11:33:56 +03:00
size_t size = sizeof ( struct dm_histogram ) +
( unsigned ) nr_bins * sizeof ( struct dm_histogram_bin ) ;
2015-08-19 22:39:10 +03:00
return dm_zalloc ( size ) ;
}
/*
* Parse a histogram bounds string supplied by the user . The string
* consists of a list of numbers , " n1,n2,n3,... " with optional ' ns ' ,
* ' us ' , ' ms ' , or ' s ' unit suffixes .
*
* The scale parameter indicates the timescale used for this region : one
* for nanoscale resolution and NSEC_PER_MSEC for miliseconds .
*
* On return bounds contains a pointer to an array of uint64_t
* histogram bounds values expressed in units of nanoseconds .
*/
struct dm_histogram * dm_histogram_bounds_from_string ( const char * bounds_str )
{
2016-02-22 16:11:02 +03:00
static const char _valid_chars [ ] = " 0123456789,muns " ;
2015-09-07 22:08:10 +03:00
uint64_t this_val = 0 , mult = 1 ;
const char * c , * v , * val_start ;
2015-08-19 22:39:10 +03:00
struct dm_histogram_bin * cur ;
2015-09-07 22:08:10 +03:00
struct dm_histogram * dmh ;
2015-08-19 22:39:10 +03:00
int nr_entries = 1 ;
2015-09-07 22:08:10 +03:00
char * endptr ;
2015-08-19 22:39:10 +03:00
c = bounds_str ;
/* Count number of bounds entries. */
while ( * c )
if ( * ( c + + ) = = ' , ' )
nr_entries + + ;
c = bounds_str ;
if ( ! ( dmh = _alloc_dm_histogram ( nr_entries ) ) )
2015-09-06 01:56:30 +03:00
return_0 ;
2015-08-19 22:39:10 +03:00
dmh - > nr_bins = nr_entries ;
cur = dmh - > bins ;
do {
2015-09-06 01:56:30 +03:00
for ( v = _valid_chars ; * v ; v + + )
2015-08-19 22:39:10 +03:00
if ( * c = = * v )
break ;
2015-09-06 01:56:30 +03:00
if ( ! * v ) {
stack ;
2015-08-19 22:39:10 +03:00
goto badchar ;
2015-09-06 01:56:30 +03:00
}
2015-08-19 22:39:10 +03:00
if ( * c = = ' , ' ) {
log_error ( " Empty histogram bin not allowed: %s " ,
bounds_str ) ;
2015-09-06 01:56:30 +03:00
goto bad ;
2015-08-19 22:39:10 +03:00
} else {
2015-09-07 22:08:10 +03:00
val_start = c ;
endptr = NULL ;
2015-08-19 22:39:10 +03:00
this_val = strtoull ( val_start , & endptr , 10 ) ;
if ( ! endptr ) {
log_error ( " Could not parse histogram bound. " ) ;
2015-09-06 01:56:30 +03:00
goto bad ;
2015-08-19 22:39:10 +03:00
}
c = endptr ; /* Advance to units, comma, or end. */
if ( * c = = ' s ' ) {
mult = NSEC_PER_SEC ;
c + + ; /* Advance over 's'. */
} else if ( * ( c + 1 ) = = ' s ' ) {
if ( * c = = ' m ' )
mult = NSEC_PER_MSEC ;
else if ( * c = = ' u ' )
mult = NSEC_PER_USEC ;
else if ( * c = = ' n ' )
mult = 1 ;
2015-09-06 01:56:30 +03:00
else {
stack ;
2015-08-19 22:39:10 +03:00
goto badchar ;
2015-09-06 01:56:30 +03:00
}
2015-08-19 22:39:10 +03:00
c + = 2 ; /* Advance over 'ms', 'us', or 'ns'. */
} else if ( * c = = ' , ' )
c + + ;
2015-09-06 01:56:30 +03:00
else if ( * c ) { /* Expected ',' or NULL. */
stack ;
2015-08-19 22:39:10 +03:00
goto badchar ;
2015-09-06 01:56:30 +03:00
}
2015-08-19 22:39:10 +03:00
if ( * c = = ' , ' )
c + + ;
this_val * = mult ;
( cur + + ) - > upper = this_val ;
}
} while ( * c ) ;
/* Bounds histograms have no owner. */
dmh - > dms = NULL ;
dmh - > region = NULL ;
return dmh ;
badchar :
log_error ( " Invalid character in histogram: %c " , * c ) ;
2015-09-06 01:56:30 +03:00
bad :
2015-08-19 22:39:10 +03:00
dm_free ( dmh ) ;
return NULL ;
}
struct dm_histogram * dm_histogram_bounds_from_uint64 ( const uint64_t * bounds )
{
2015-09-07 22:08:10 +03:00
const uint64_t * entry = bounds ;
2015-08-19 22:39:10 +03:00
struct dm_histogram_bin * cur ;
2015-09-07 22:08:10 +03:00
struct dm_histogram * dmh ;
2015-08-19 22:39:10 +03:00
int nr_entries = 1 ;
if ( ! bounds | | ! bounds [ 0 ] ) {
log_error ( " Could not parse empty histogram bounds array " ) ;
return 0 ;
}
/* Count number of bounds entries. */
while ( * entry )
if ( * ( + + entry ) )
nr_entries + + ;
entry = bounds ;
if ( ! ( dmh = _alloc_dm_histogram ( nr_entries ) ) )
return_0 ;
dmh - > nr_bins = nr_entries ;
cur = dmh - > bins ;
while ( * entry )
( cur + + ) - > upper = * ( entry + + ) ;
/* Bounds histograms have no owner. */
dmh - > dms = NULL ;
dmh - > region = NULL ;
return dmh ;
}
void dm_histogram_bounds_destroy ( struct dm_histogram * bounds )
{
if ( ! bounds )
return ;
/* Bounds histograms are not bound to any handle or region. */
if ( bounds - > dms | | bounds - > region ) {
log_error ( " Freeing invalid histogram bounds pointer %p. " ,
( void * ) bounds ) ;
stack ;
}
/* dm_free() expects a (void *). */
dm_free ( ( void * ) bounds ) ;
}
/*
* Scale a bounds value down from nanoseconds to the largest possible
* whole unit suffix .
*/
static void _scale_bound_value_to_suffix ( uint64_t * bound , const char * * suffix )
{
2015-09-07 22:11:28 +03:00
* suffix = " ns " ;
2015-08-19 22:39:10 +03:00
if ( ! ( * bound % NSEC_PER_SEC ) ) {
* bound / = NSEC_PER_SEC ;
* suffix = " s " ;
} else if ( ! ( * bound % NSEC_PER_MSEC ) ) {
* bound / = NSEC_PER_MSEC ;
* suffix = " ms " ;
} else if ( ! ( * bound % NSEC_PER_USEC ) ) {
* bound / = NSEC_PER_USEC ;
* suffix = " us " ;
}
}
# define DM_HISTOGRAM_BOUNDS_MASK 0x30
2016-06-16 21:40:43 +03:00
# define BOUNDS_LEN 64
2015-08-19 22:39:10 +03:00
static int _make_bounds_string ( char * buf , size_t size , uint64_t lower ,
uint64_t upper , int flags , int width )
{
2016-06-16 21:40:43 +03:00
char bound_buf [ BOUNDS_LEN ] ;
2015-08-19 22:39:10 +03:00
const char * l_suff = NULL ;
const char * u_suff = NULL ;
2015-09-03 23:48:15 +03:00
const char * sep = " " ;
2015-08-19 22:39:10 +03:00
int bounds = flags & DM_HISTOGRAM_BOUNDS_MASK ;
if ( ! bounds )
2015-09-06 01:56:30 +03:00
return_0 ;
2015-08-19 22:39:10 +03:00
* buf = ' \0 ' ;
if ( flags & DM_HISTOGRAM_SUFFIX ) {
_scale_bound_value_to_suffix ( & lower , & l_suff ) ;
_scale_bound_value_to_suffix ( & upper , & u_suff ) ;
} else
l_suff = u_suff = " " ;
2015-09-03 23:48:15 +03:00
if ( flags & DM_HISTOGRAM_VALUES )
sep = " : " ;
2015-08-19 22:39:10 +03:00
if ( bounds > DM_HISTOGRAM_BOUNDS_LOWER ) {
/* Handle infinite uppermost bound. */
if ( upper = = UINT64_MAX ) {
if ( dm_snprintf ( bound_buf , sizeof ( bound_buf ) ,
" > " FMTu64 " %s " , lower , l_suff ) < 0 )
goto_out ;
/* Only display an 'upper' string for final bin. */
bounds = DM_HISTOGRAM_BOUNDS_UPPER ;
} else {
if ( dm_snprintf ( bound_buf , sizeof ( bound_buf ) ,
FMTu64 " %s " , upper , u_suff ) < 0 )
goto_out ;
}
} else if ( bounds = = DM_HISTOGRAM_BOUNDS_LOWER ) {
if ( ( dm_snprintf ( bound_buf , sizeof ( bound_buf ) , FMTu64 " %s " ,
lower , l_suff ) ) < 0 )
goto_out ;
}
switch ( bounds ) {
case DM_HISTOGRAM_BOUNDS_LOWER :
case DM_HISTOGRAM_BOUNDS_UPPER :
return dm_snprintf ( buf , size , " %*s%s " , width , bound_buf , sep ) ;
case DM_HISTOGRAM_BOUNDS_RANGE :
return dm_snprintf ( buf , size , FMTu64 " %s-%s%s " ,
lower , l_suff , bound_buf , sep ) ;
}
out :
return 0 ;
}
2015-09-03 23:48:15 +03:00
# define BOUND_WIDTH_NOSUFFIX 10 /* 999999999 nsecs */
2015-08-19 22:39:10 +03:00
# define BOUND_WIDTH 6 /* bounds string up to 9999xs */
# define COUNT_WIDTH 6 /* count string: up to 9999 */
2015-09-03 23:48:15 +03:00
# define PERCENT_WIDTH 6 /* percent string : 0.00-100.00% */
# define DM_HISTOGRAM_VALUES_MASK 0x06
2015-08-19 22:39:10 +03:00
const char * dm_histogram_to_string ( const struct dm_histogram * dmh , int bin ,
int width , int flags )
{
2016-06-16 21:40:43 +03:00
char buf [ BOUNDS_LEN ] , bounds_buf [ BOUNDS_LEN ] ;
2015-08-19 22:39:10 +03:00
int minwidth , bounds , values , start , last ;
2015-09-07 22:08:10 +03:00
uint64_t lower , upper , val_u64 ; /* bounds of the current bin. */
2015-08-19 22:39:10 +03:00
/* Use the histogram pool for string building. */
struct dm_pool * mem = dmh - > dms - > hist_mem ;
const char * sep = " " ;
2015-09-07 22:08:10 +03:00
int bounds_width ;
2015-09-04 13:40:19 +03:00
ssize_t len = 0 ;
2015-09-07 22:08:10 +03:00
float val_flt ;
2015-08-19 22:39:10 +03:00
bounds = flags & DM_HISTOGRAM_BOUNDS_MASK ;
values = flags & DM_HISTOGRAM_VALUES ;
if ( bin < 0 ) {
start = 0 ;
last = dmh - > nr_bins - 1 ;
} else
start = last = bin ;
2015-09-04 13:40:19 +03:00
minwidth = width ;
2015-09-03 23:48:15 +03:00
if ( width < 0 | | ! values )
width = minwidth = 0 ; /* no padding */
else if ( flags & DM_HISTOGRAM_PERCENT )
2015-08-19 22:39:10 +03:00
width = minwidth = ( width ) ? : PERCENT_WIDTH ;
else if ( flags & DM_HISTOGRAM_VALUES )
width = minwidth = ( width ) ? : COUNT_WIDTH ;
2015-09-03 23:48:15 +03:00
if ( values & & ! width )
sep = " : " ;
2015-08-19 22:39:10 +03:00
/* Set bounds string to the empty string. */
bounds_buf [ 0 ] = ' \0 ' ;
2015-09-07 14:01:20 +03:00
if ( ! dm_pool_begin_object ( mem , 64 ) )
return_0 ;
2015-08-19 22:39:10 +03:00
for ( bin = start ; bin < = last ; bin + + ) {
if ( bounds ) {
2015-09-03 23:48:15 +03:00
/* Default bounds width depends on time suffixes. */
bounds_width = ( ! ( flags & DM_HISTOGRAM_SUFFIX ) )
? BOUND_WIDTH_NOSUFFIX
: BOUND_WIDTH ;
bounds_width = ( ! width ) ? width : bounds_width ;
2015-08-19 22:39:10 +03:00
lower = dm_histogram_get_bin_lower ( dmh , bin ) ;
upper = dm_histogram_get_bin_upper ( dmh , bin ) ;
2015-09-03 23:48:15 +03:00
len = sizeof ( bounds_buf ) ;
len = _make_bounds_string ( bounds_buf , len ,
lower , upper , flags ,
bounds_width ) ;
/*
* Comma separates " bounds: value " pairs unless
* - - noheadings is used .
*/
sep = ( width | | ! values ) ? " , " : " : " ;
/* Adjust width by real bounds length if set. */
width - = ( width ) ? ( len - ( bounds_width + 1 ) ) : 0 ;
/* -ve width indicates specified width was overrun. */
2015-08-19 22:39:10 +03:00
width = ( width > 0 ) ? width : 0 ;
}
if ( bin = = last )
sep = " " ;
2015-09-03 23:48:15 +03:00
2015-08-19 22:39:10 +03:00
if ( flags & DM_HISTOGRAM_PERCENT ) {
dm_percent_t pr ;
pr = dm_histogram_get_bin_percent ( dmh , bin ) ;
2015-09-07 22:08:10 +03:00
val_flt = dm_percent_to_float ( pr ) ;
2015-08-19 22:39:10 +03:00
len = dm_snprintf ( buf , sizeof ( buf ) , " %s%*.2f%%%s " ,
2015-09-07 22:08:10 +03:00
bounds_buf , width , val_flt , sep ) ;
2015-08-19 22:39:10 +03:00
} else if ( values ) {
2015-09-07 22:08:10 +03:00
val_u64 = dmh - > bins [ bin ] . count ;
2015-08-19 22:39:10 +03:00
len = dm_snprintf ( buf , sizeof ( buf ) , " %s%* " PRIu64 " %s " ,
2015-09-07 22:08:10 +03:00
bounds_buf , width , val_u64 , sep ) ;
2015-08-19 22:39:10 +03:00
} else if ( bounds )
len = dm_snprintf ( buf , sizeof ( buf ) , " %s%s " , bounds_buf ,
sep ) ;
2015-11-08 19:21:22 +03:00
else {
* buf = ' \0 ' ;
len = 0 ;
}
2015-08-19 22:39:10 +03:00
if ( len < 0 )
2015-09-06 01:56:30 +03:00
goto_bad ;
2015-08-19 22:39:10 +03:00
width = minwidth ; /* re-set histogram column width. */
2015-09-07 14:01:20 +03:00
if ( ! dm_pool_grow_object ( mem , buf , ( size_t ) len ) )
goto_bad ;
2015-08-19 22:39:10 +03:00
}
2015-09-07 14:01:20 +03:00
if ( ! dm_pool_grow_object ( mem , " \0 " , 1 ) )
goto_bad ;
2015-08-19 22:39:10 +03:00
return ( const char * ) dm_pool_end_object ( mem ) ;
2015-09-06 01:56:30 +03:00
bad :
2015-08-19 22:39:10 +03:00
dm_pool_abandon_object ( mem ) ;
return NULL ;
}
2016-02-29 20:52:29 +03:00
/*
* A lightweight representation of an extent ( region , area , file
* system block or extent etc . ) . A table of extents can be used
* to sort and to efficiently find holes or overlaps among a set
* of tuples of the form ( id , start , len ) .
*/
struct _extent {
struct dm_list list ;
uint64_t id ;
uint64_t start ;
uint64_t len ;
} ;
/* last address in an extent */
# define _extent_end(a) ((a)->start + (a)->len - 1)
/* a and b must be sorted by increasing start sector */
# define _extents_overlap(a, b) (_extent_end(a) > (b)->start)
/*
* Comparison function to sort extents in ascending start order .
*/
static int _extent_start_compare ( const void * p1 , const void * p2 )
{
struct _extent * r1 , * r2 ;
r1 = ( struct _extent * ) p1 ;
r2 = ( struct _extent * ) p2 ;
if ( r1 - > start < r2 - > start )
return - 1 ;
else if ( r1 - > start = = r2 - > start )
return 0 ;
return 1 ;
}
static int _stats_create_group ( struct dm_stats * dms , dm_bitset_t regions ,
const char * alias , uint64_t * group_id )
{
struct dm_stats_group * group ;
* group_id = dm_bit_get_first ( regions ) ;
/* group has no regions? */
if ( * group_id = = DM_STATS_GROUP_NOT_PRESENT )
return_0 ;
group = & dms - > groups [ * group_id ] ;
if ( group - > regions ) {
log_error ( INTERNAL_ERROR " Unexpected group state while "
" creating group ID bitmap " FMTu64 , * group_id ) ;
return 0 ;
}
group - > group_id = * group_id ;
group - > regions = regions ;
if ( alias )
group - > alias = dm_strdup ( alias ) ;
else
group - > alias = NULL ;
/* force an update of the group tag stored in aux_data */
if ( ! _stats_set_aux ( dms , * group_id , dms - > regions [ * group_id ] . aux_data ) )
return 0 ;
return 1 ;
}
static int _stats_group_check_overlap ( const struct dm_stats * dms ,
dm_bitset_t regions , int count )
{
struct dm_list ext_list = DM_LIST_HEAD_INIT ( ext_list ) ;
struct _extent * ext , * tmp , * next , * map = NULL ;
size_t map_size = ( dms - > max_region + 1 ) * sizeof ( * map ) ;
int i = 0 , id , overlap , merged ;
map = dm_pool_alloc ( dms - > mem , map_size ) ;
if ( ! map ) {
log_error ( " Could not allocate memory for region map " ) ;
return 0 ;
}
2016-07-08 13:05:38 +03:00
/* build a table of extents in order of region_id */
2016-02-29 20:52:29 +03:00
for ( id = dm_bit_get_first ( regions ) ; id > = 0 ;
id = dm_bit_get_next ( regions , id ) ) {
dm_list_init ( & map [ i ] . list ) ;
map [ i ] . id = id ;
map [ i ] . start = dms - > regions [ id ] . start ;
map [ i ] . len = dms - > regions [ id ] . len ;
i + + ;
}
2016-12-13 01:49:31 +03:00
/* A single region cannot overlap itself. */
if ( i = = 1 ) {
dm_pool_free ( dms - > mem , map ) ;
return 1 ;
}
2016-07-08 13:05:38 +03:00
/* sort by extent.start */
2016-02-29 20:52:29 +03:00
qsort ( map , count , sizeof ( * map ) , _extent_start_compare ) ;
for ( i = 0 ; i < count ; i + + )
dm_list_add ( & ext_list , & map [ i ] . list ) ;
overlap = 0 ;
merge :
merged = 0 ;
dm_list_iterate_items_safe ( ext , tmp , & ext_list ) {
next = dm_list_item ( dm_list_next ( & ext_list , & ext - > list ) ,
struct _extent ) ;
if ( ! next )
continue ;
if ( _extents_overlap ( ext , next ) ) {
2016-10-26 15:42:13 +03:00
log_warn ( " WARNING: region IDs " FMTu64 " and "
2016-02-29 20:52:29 +03:00
FMTu64 " overlap. Some events will be "
" counted twice. " , ext - > id , next - > id ) ;
/* merge larger extent into smaller */
if ( _extent_end ( ext ) > _extent_end ( next ) ) {
next - > id = ext - > id ;
next - > len = ext - > len ;
}
if ( ext - > start < next - > start )
next - > start = ext - > start ;
dm_list_del ( & ext - > list ) ;
overlap = merged = 1 ;
}
}
2016-07-08 13:05:38 +03:00
/* continue until no merge candidates remain */
2016-02-29 20:52:29 +03:00
if ( merged )
goto merge ;
dm_pool_free ( dms - > mem , map ) ;
2016-12-13 01:49:31 +03:00
return ( overlap = = 0 ) ;
2016-02-29 20:52:29 +03:00
}
2016-07-04 00:55:33 +03:00
static void _stats_copy_histogram_bounds ( struct dm_histogram * to ,
struct dm_histogram * from )
{
uint64_t i ;
to - > nr_bins = from - > nr_bins ;
for ( i = 0 ; i < to - > nr_bins ; i + + )
to - > bins [ i ] . upper = from - > bins [ i ] . upper ;
}
/*
* Compare histogram bounds h1 and h2 , and return 1 if they match ( i . e .
* have the same number of bins and identical bin boundary values ) , or 0
* otherwise .
*/
static int _stats_check_histogram_bounds ( struct dm_histogram * h1 ,
struct dm_histogram * h2 )
{
uint64_t i ;
if ( ! h1 | | ! h2 )
return 0 ;
if ( h1 - > nr_bins ! = h2 - > nr_bins )
return 0 ;
for ( i = 0 ; i < h1 - > nr_bins ; i + + )
if ( h1 - > bins [ i ] . upper ! = h2 - > bins [ i ] . upper )
return 0 ;
return 1 ;
}
2016-02-29 20:52:29 +03:00
/*
* Create a new group in stats handle dms from the group description
* passed in group .
*/
int dm_stats_create_group ( struct dm_stats * dms , const char * members ,
const char * alias , uint64_t * group_id )
{
2016-07-04 00:55:33 +03:00
struct dm_histogram * check = NULL , * bounds ;
2016-02-29 20:52:29 +03:00
int i , count = 0 , precise = 0 ;
dm_bitset_t regions ;
if ( ! dms - > regions | | ! dms - > groups ) {
log_error ( " Could not create group: no regions found. " ) ;
return 0 ;
} ;
2016-12-12 01:41:45 +03:00
if ( ! ( regions = dm_bitset_parse_list ( members , NULL , 0 ) ) ) {
2016-02-29 20:52:29 +03:00
log_error ( " Could not parse list: '%s' " , members ) ;
2016-07-04 00:55:33 +03:00
return 0 ;
}
if ( ! ( check = dm_pool_zalloc ( dms - > hist_mem , sizeof ( * check ) ) ) ) {
log_error ( " Could not allocate memory for bounds check " ) ;
2016-02-29 20:52:29 +03:00
goto bad ;
}
/* too many bits? */
if ( ( * regions - 1 ) > dms - > max_region ) {
log_error ( " Invalid region ID: %d " , * regions - 1 ) ;
goto bad ;
}
2016-07-08 13:05:38 +03:00
/*
* Check that each region_id in the bitmap meets the group
* constraints : present , not already grouped , and if any
* histogram is present that they all have the same bounds .
*/
2016-02-29 20:52:29 +03:00
for ( i = dm_bit_get_first ( regions ) ; i > = 0 ;
i = dm_bit_get_next ( regions , i ) ) {
if ( ! dm_stats_region_present ( dms , i ) ) {
log_error ( " Region ID %d does not exist " , i ) ;
goto bad ;
}
if ( _stats_region_is_grouped ( dms , i ) ) {
log_error ( " Region ID %d already a member of group ID "
FMTu64 , i , dms - > regions [ i ] . group_id ) ;
goto bad ;
}
if ( dms - > regions [ i ] . timescale = = 1 )
precise + + ;
2016-07-04 00:55:33 +03:00
/* check for matching histogram bounds */
bounds = dms - > regions [ i ] . bounds ;
if ( bounds & & ! check - > nr_bins )
_stats_copy_histogram_bounds ( check , bounds ) ;
else if ( bounds ) {
if ( ! _stats_check_histogram_bounds ( check , bounds ) ) {
log_error ( " All region histogram bounds "
" must match exactly " ) ;
goto bad ;
}
}
2016-02-29 20:52:29 +03:00
count + + ;
}
if ( precise & & ( precise ! = count ) )
2016-10-26 15:42:13 +03:00
log_warn ( " WARNING: Grouping regions with different clock resolution: "
" precision may be lost. " ) ;
2016-02-29 20:52:29 +03:00
if ( ! _stats_group_check_overlap ( dms , regions , count ) )
2016-11-01 13:02:01 +03:00
log_very_verbose ( " Creating group with overlapping regions. " ) ;
2016-02-29 20:52:29 +03:00
if ( ! _stats_create_group ( dms , regions , alias , group_id ) )
goto bad ;
2016-07-04 00:55:33 +03:00
dm_pool_free ( dms - > hist_mem , check ) ;
2016-02-29 20:52:29 +03:00
return 1 ;
2016-07-04 00:55:33 +03:00
2016-02-29 20:52:29 +03:00
bad :
2016-07-04 00:55:33 +03:00
dm_pool_free ( dms - > hist_mem , check ) ;
2016-02-29 20:52:29 +03:00
dm_bitset_destroy ( regions ) ;
return 0 ;
}
/*
* Remove the specified group_id .
*/
2016-07-01 15:14:41 +03:00
int dm_stats_delete_group ( struct dm_stats * dms , uint64_t group_id ,
int remove_regions )
2016-02-29 20:52:29 +03:00
{
2016-07-01 15:14:41 +03:00
struct dm_stats_region * leader ;
dm_bitset_t regions ;
uint64_t i ;
2016-02-29 20:52:29 +03:00
if ( group_id > dms - > max_region ) {
log_error ( " Invalid group ID: " FMTu64 , group_id ) ;
return 0 ;
}
if ( ! _stats_group_id_present ( dms , group_id ) ) {
log_error ( " Group ID " FMTu64 " does not exist " , group_id ) ;
return 0 ;
}
2016-07-01 15:14:41 +03:00
regions = dms - > groups [ group_id ] . regions ;
leader = & dms - > regions [ group_id ] ;
/* delete all but the group leader */
for ( i = ( * regions - 1 ) ; i > leader - > region_id ; i - - ) {
if ( dm_bit ( regions , i ) ) {
dm_bit_clear ( regions , i ) ;
if ( remove_regions & & ! dm_stats_delete_region ( dms , i ) )
2016-10-26 15:42:13 +03:00
log_warn ( " WARNING: Failed to delete region "
2016-07-01 15:14:41 +03:00
FMTu64 " on %s. " , i , dms - > name ) ;
}
}
2016-07-08 13:05:38 +03:00
/* clear group and mark as not present */
2016-02-29 20:52:29 +03:00
_stats_clear_group_regions ( dms , group_id ) ;
_stats_group_destroy ( & dms - > groups [ group_id ] ) ;
2016-07-08 13:05:38 +03:00
/* delete leader or clear aux_data */
2016-07-01 15:14:41 +03:00
if ( remove_regions )
return dm_stats_delete_region ( dms , group_id ) ;
else if ( ! _stats_set_aux ( dms , group_id , leader - > aux_data ) )
2016-02-29 20:52:29 +03:00
return 0 ;
2016-07-01 15:14:41 +03:00
2016-02-29 20:52:29 +03:00
return 1 ;
}
uint64_t dm_stats_get_group_id ( const struct dm_stats * dms , uint64_t region_id )
{
2016-06-19 15:30:46 +03:00
region_id = ( region_id = = DM_STATS_REGION_CURRENT )
? dms - > cur_region : region_id ;
if ( region_id & DM_STATS_WALK_GROUP ) {
if ( region_id = = DM_STATS_WALK_GROUP )
return dms - > cur_group ;
else
return region_id & ~ DM_STATS_WALK_GROUP ;
}
if ( region_id & DM_STATS_WALK_REGION )
region_id & = ~ DM_STATS_WALK_REGION ;
2016-02-29 20:52:29 +03:00
return dms - > regions [ region_id ] . group_id ;
}
int dm_stats_get_group_descriptor ( const struct dm_stats * dms ,
uint64_t group_id , char * * buf )
{
dm_bitset_t regions = dms - > groups [ group_id ] . regions ;
size_t buflen ;
buflen = _stats_group_tag_len ( dms , regions ) ;
* buf = dm_pool_alloc ( dms - > mem , buflen ) ;
if ( ! * buf ) {
log_error ( " Could not allocate memory for regions string " ) ;
return 0 ;
}
if ( ! _stats_group_tag_fill ( dms , regions , * buf , buflen ) )
return 0 ;
return 1 ;
}
2016-07-09 01:38:32 +03:00
# ifdef HAVE_LINUX_FIEMAP_H
2016-06-30 23:46:43 +03:00
/*
* Group a table of region_ids corresponding to the extents of a file .
*/
static int _stats_group_file_regions ( struct dm_stats * dms , uint64_t * region_ids ,
uint64_t count , const char * alias )
{
dm_bitset_t regions = dm_bitset_create ( NULL , dms - > nr_regions ) ;
uint64_t i , group_id = DM_STATS_GROUP_NOT_PRESENT ;
char * members = NULL ;
int buflen ;
if ( ! regions ) {
log_error ( " Cannot map file: failed to allocate group bitmap. " ) ;
return 0 ;
}
for ( i = 0 ; i < count ; i + + )
dm_bit_set ( regions , region_ids [ i ] ) ;
buflen = _stats_group_tag_len ( dms , regions ) ;
members = dm_malloc ( buflen ) ;
if ( ! members ) {
log_error ( " Cannot map file: failed to allocate group "
" descriptor. " ) ;
dm_bitset_destroy ( regions ) ;
return 0 ;
}
if ( ! _stats_group_tag_fill ( dms , regions , members , buflen ) )
goto bad ;
/*
* overlaps should not be possible : overlapping file extents
* returned by FIEMAP imply a kernel bug or a corrupt fs .
*/
if ( ! _stats_group_check_overlap ( dms , regions , count ) )
2016-11-01 13:02:01 +03:00
log_very_verbose ( " Creating group with overlapping regions. " ) ;
2016-06-30 23:46:43 +03:00
if ( ! _stats_create_group ( dms , regions , alias , & group_id ) )
goto bad ;
dm_free ( members ) ;
return 1 ;
bad :
dm_bitset_destroy ( regions ) ;
dm_free ( members ) ;
return 0 ;
}
static int _stats_add_extent ( struct dm_pool * mem , struct fiemap_extent * fm_ext ,
uint64_t id )
{
struct _extent extent ;
/* final address of list is unknown */
memset ( & extent . list , 0 , sizeof ( extent . list ) ) ;
/* convert bytes to dm (512b) sectors */
2016-07-08 23:12:18 +03:00
extent . start = fm_ext - > fe_physical > > SECTOR_SHIFT ;
extent . len = fm_ext - > fe_length > > SECTOR_SHIFT ;
2016-06-30 23:46:43 +03:00
extent . id = id ;
if ( ! dm_pool_grow_object ( mem , & extent ,
sizeof ( extent ) ) ) {
log_error ( " Cannot map file: failed to grow extent map. " ) ;
return 0 ;
}
return 1 ;
}
2016-07-09 00:21:14 +03:00
/* test for the boundary of an extent */
2016-12-12 23:40:27 +03:00
# define ext_boundary(ext, exp) \
( ( ext ) . fe_logical ! = 0 ) & & \
( ( ext ) . fe_physical ! = ( exp ) )
2016-07-09 00:21:14 +03:00
2016-12-13 15:03:00 +03:00
/*
* Copy fields from fiemap_extent ' from ' to the fiemap_extent
* pointed to by ' to ' .
*/
# define ext_copy(to, from) \
do { \
* ( to ) = * ( from ) ; \
} while ( 0 )
static uint64_t _stats_map_extents ( struct dm_pool * mem ,
struct fiemap * fiemap ,
struct fiemap_extent * fm_ext ,
struct fiemap_extent * fm_last ,
struct fiemap_extent * fm_pending ,
uint64_t next_extent ,
int * eof )
{
uint64_t expected = 0 , nr_extents = next_extent ;
unsigned int i ;
/*
* Loop over the returned extents adding the fm_pending extent
* to the table of extents each time a discontinuity ( or eof )
* is detected .
*
* We use a pointer to fm_pending in the caller since it is
* possible that logical extents comprising a single physical
* extent are returned by successive FIEMAP calls .
*/
for ( i = 0 ; i < fiemap - > fm_mapped_extents ; i + + ) {
expected = fm_last - > fe_physical + fm_last - > fe_length ;
if ( fm_ext [ i ] . fe_flags & FIEMAP_EXTENT_LAST )
* eof = 1 ;
/* cannot map extents that are not yet allocated. */
if ( fm_ext [ i ] . fe_flags
& ( FIEMAP_EXTENT_UNKNOWN | FIEMAP_EXTENT_DELALLOC ) )
continue ;
if ( ext_boundary ( fm_ext [ i ] , expected ) ) {
if ( ! _stats_add_extent ( mem , fm_pending , nr_extents ) )
goto_bad ;
nr_extents + + ;
/* Begin a new pending extent. */
ext_copy ( fm_pending , fm_ext + i ) ;
} else {
expected = 0 ;
/* Begin a new pending extent for extent 0. */
if ( fm_ext [ i ] . fe_logical = = 0 )
ext_copy ( fm_pending , fm_ext + i ) ;
else
/* accumulate this logical extent's length */
fm_pending - > fe_length + = fm_ext [ i ] . fe_length ;
}
* fm_last = fm_ext [ i ] ;
}
/*
* If the file only has a single extent , no boundary is ever
* detected to trigger addition of the first extent .
*/
2016-12-14 00:36:11 +03:00
if ( fm_ext [ i - 1 ] . fe_logical = = 0 ) {
2016-12-13 15:03:00 +03:00
_stats_add_extent ( mem , fm_pending , nr_extents ) ;
2016-12-14 00:36:11 +03:00
nr_extents + + ;
}
2016-12-13 15:03:00 +03:00
fiemap - > fm_start = ( fm_ext [ i - 1 ] . fe_logical +
fm_ext [ i - 1 ] . fe_length ) ;
/* return the number of extents found in this call. */
return nr_extents - next_extent ;
bad :
/* signal mapping error to caller */
* eof = - 1 ;
return 0 ;
}
2016-06-30 23:46:43 +03:00
/*
* Read the extents of an open file descriptor into a table of struct _extent .
*
* Based on e2fsprogs / misc / filefrag . c : : filefrag_fiemap ( ) .
*
* Copyright 2003 by Theodore Ts ' o .
*
*/
static struct _extent * _stats_get_extents_for_file ( struct dm_pool * mem , int fd ,
uint64_t * count )
{
2016-12-13 15:03:00 +03:00
struct fiemap_extent fm_last = { 0 } , fm_pending = { 0 } , * fm_ext = NULL ;
2016-10-03 16:42:30 +03:00
struct fiemap * fiemap = NULL ;
2016-12-13 15:03:00 +03:00
int eof = 0 , nr_extents = 0 ;
2016-06-30 23:46:43 +03:00
struct _extent * extents ;
unsigned long flags = 0 ;
2016-12-13 15:03:00 +03:00
uint64_t * buf ;
2016-06-30 23:46:43 +03:00
int rc ;
2016-10-03 16:42:30 +03:00
buf = dm_zalloc ( STATS_FIE_BUF_LEN ) ;
if ( ! buf ) {
log_error ( " Could not allocate memory for FIEMAP buffer. " ) ;
return NULL ;
}
/* initialise pointers into the ioctl buffer. */
fiemap = ( struct fiemap * ) buf ;
fm_ext = & fiemap - > fm_extents [ 0 ] ;
2016-06-30 23:46:43 +03:00
/* space available per ioctl */
2016-10-03 16:42:30 +03:00
* count = ( STATS_FIE_BUF_LEN - sizeof ( * fiemap ) )
2016-06-30 23:46:43 +03:00
/ sizeof ( struct fiemap_extent ) ;
/* grow temporary extent table in the pool */
if ( ! dm_pool_begin_object ( mem , sizeof ( * extents ) ) )
return NULL ;
flags | = FIEMAP_FLAG_SYNC ;
do {
/* start of ioctl loop - zero size and set count to bufsize */
fiemap - > fm_length = ~ 0ULL ;
fiemap - > fm_flags = flags ;
fiemap - > fm_extent_count = * count ;
/* get count-sized chunk of extents */
rc = ioctl ( fd , FS_IOC_FIEMAP , ( unsigned long ) fiemap ) ;
if ( rc < 0 ) {
rc = - errno ;
if ( rc = = - EBADR )
log_err_once ( " FIEMAP failed with unknown "
" flags %x. " , fiemap - > fm_flags ) ;
goto bad ;
}
2016-12-13 15:03:00 +03:00
/* If 0 extents are returned, more ioctls are not needed */
2016-06-30 23:46:43 +03:00
if ( fiemap - > fm_mapped_extents = = 0 )
break ;
2016-12-13 15:03:00 +03:00
nr_extents + = _stats_map_extents ( mem , fiemap , fm_ext , & fm_last ,
& fm_pending , nr_extents , & eof ) ;
2016-12-12 23:40:27 +03:00
2016-12-13 15:03:00 +03:00
/* check for extent mapping error */
if ( eof < 0 )
goto bad ;
2016-12-12 23:40:27 +03:00
2016-12-13 15:03:00 +03:00
} while ( eof = = 0 ) ;
2016-06-30 23:46:43 +03:00
2016-12-13 15:03:00 +03:00
if ( ! nr_extents ) {
2016-06-30 23:46:43 +03:00
log_error ( " Cannot map file: no allocated extents. " ) ;
goto bad ;
}
/* return total number of extents */
2016-12-13 15:03:00 +03:00
* count = nr_extents ;
2016-10-03 16:42:30 +03:00
extents = dm_pool_end_object ( mem ) ;
/* free FIEMAP buffer. */
dm_free ( buf ) ;
return extents ;
2016-06-30 23:46:43 +03:00
bad :
dm_pool_abandon_object ( mem ) ;
2016-10-03 16:42:30 +03:00
dm_free ( buf ) ;
2016-06-30 23:46:43 +03:00
return NULL ;
}
/*
* Create a set of regions representing the extents of a file and
* return a table of uint64_t region_id values . The number of regions
* created is returned in the memory pointed to by count ( which must be
* non - NULL ) .
*/
static uint64_t * _stats_create_file_regions ( struct dm_stats * dms , int fd ,
struct dm_histogram * bounds ,
int precise , uint64_t * count )
{
2016-12-10 16:28:21 +03:00
uint64_t * regions = NULL , i , fail_region ;
2016-12-10 16:07:03 +03:00
struct dm_pool * extent_mem = NULL ;
2016-06-30 23:46:43 +03:00
struct _extent * extents = NULL ;
2016-07-07 11:47:19 +03:00
char * hist_arg = NULL ;
2016-06-30 23:46:43 +03:00
struct statfs fsbuf ;
struct stat buf ;
2016-07-09 01:38:32 +03:00
# ifdef BTRFS_SUPER_MAGIC
2016-06-30 23:46:43 +03:00
if ( fstatfs ( fd , & fsbuf ) ) {
log_error ( " fstatfs failed for fd %d " , fd ) ;
return 0 ;
}
if ( fsbuf . f_type = = BTRFS_SUPER_MAGIC ) {
log_error ( " Cannot map file: btrfs does not provide "
" physical FIEMAP extent data. " ) ;
return 0 ;
}
2016-07-09 01:38:32 +03:00
# endif
2016-06-30 23:46:43 +03:00
if ( fstat ( fd , & buf ) ) {
log_error ( " fstat failed for fd %d " , fd ) ;
return 0 ;
}
if ( ! ( buf . st_mode & S_IFREG ) ) {
log_error ( " Not a regular file " ) ;
return 0 ;
}
if ( ! dm_is_dm_major ( major ( buf . st_dev ) ) ) {
log_error ( " Cannot map file: not a device-mapper device. " ) ;
return 0 ;
}
2016-12-10 16:07:03 +03:00
/* Use a temporary, private pool for the extent table. This avoids
* hijacking the dms - > mem ( region table ) pool which would lead to
* interleaving temporary allocations with dm_stats_list ( ) data ,
* causing complications in the error path .
*/
if ( ! ( extent_mem = dm_pool_create ( " extents " , sizeof ( * extents ) ) ) )
2016-06-30 23:46:43 +03:00
return_0 ;
2016-12-10 16:07:03 +03:00
if ( ! ( extents = _stats_get_extents_for_file ( extent_mem , fd , count ) ) ) {
dm_pool_destroy ( extent_mem ) ;
return_0 ;
}
2016-07-07 11:47:19 +03:00
if ( bounds ) {
/* _build_histogram_arg enables precise if vals < 1ms. */
if ( ! ( hist_arg = _build_histogram_arg ( bounds , & precise ) ) )
goto_out ;
}
2016-06-30 23:46:43 +03:00
/* make space for end-of-table marker */
if ( ! ( regions = dm_malloc ( ( 1 + * count ) * sizeof ( * regions ) ) ) ) {
log_error ( " Could not allocate memory for region IDs. " ) ;
goto out ;
}
for ( i = 0 ; i < * count ; i + + ) {
2016-07-07 11:47:19 +03:00
if ( ! _stats_create_region ( dms , regions + i , extents [ i ] . start ,
extents [ i ] . len , - 1 , precise , hist_arg ,
dms - > program_id , " " ) ) {
2016-06-30 23:46:43 +03:00
log_error ( " Failed to create region " FMTu64 " of "
FMTu64 " at " FMTu64 " . " , i , * count ,
extents [ i ] . start ) ;
goto out_remove ;
}
}
regions [ * count ] = DM_STATS_REGION_NOT_PRESENT ;
2016-07-18 20:08:51 +03:00
if ( bounds )
dm_free ( hist_arg ) ;
2016-12-10 16:07:03 +03:00
dm_pool_free ( extent_mem , extents ) ;
dm_pool_destroy ( extent_mem ) ;
2016-06-30 23:46:43 +03:00
return regions ;
out_remove :
2016-12-10 02:59:51 +03:00
/* New region creation may begin to fail part-way through creating
* a set of file mapped regions : in this case we need to roll back
* the regions that were already created and return the handle to
* a consistent state . A listed handle is required for this : use a
* single list operation and call _stats_delete_region ( ) directly
* to avoid a @ stats_list ioctl and list parsing for each region .
*/
dm_stats_list ( dms , NULL ) ;
2016-12-10 16:28:21 +03:00
fail_region = i ;
for ( i = 0 ; i < fail_region ; i + + )
2016-12-10 02:59:51 +03:00
if ( ! _stats_delete_region ( dms , regions [ i ] ) )
2016-06-30 23:46:43 +03:00
log_error ( " Could not delete region " FMTu64 " . " , i ) ;
2016-12-10 02:59:51 +03:00
* count = 0 ;
2016-06-30 23:46:43 +03:00
out :
2016-12-10 16:07:03 +03:00
dm_pool_free ( extent_mem , extents ) ;
dm_pool_destroy ( extent_mem ) ;
2016-07-18 20:08:51 +03:00
dm_free ( hist_arg ) ;
2016-06-30 23:46:43 +03:00
dm_free ( regions ) ;
return NULL ;
}
uint64_t * dm_stats_create_regions_from_fd ( struct dm_stats * dms , int fd ,
int group , int precise ,
struct dm_histogram * bounds ,
const char * alias )
{
uint64_t * regions , count = 0 ;
if ( alias & & ! group ) {
log_error ( " Cannot set alias without grouping regions. " ) ;
return NULL ;
}
regions = _stats_create_file_regions ( dms , fd , bounds , precise , & count ) ;
if ( ! regions )
return_0 ;
if ( ! group )
return regions ;
/* refresh handle */
if ( ! dm_stats_list ( dms , NULL ) )
goto_out ;
if ( ! _stats_group_file_regions ( dms , regions , count , alias ) )
goto_out ;
return regions ;
out :
dm_free ( regions ) ;
return NULL ;
}
2016-07-09 01:38:32 +03:00
# else /* HAVE_LINUX_FIEMAP */
uint64_t * dm_stats_create_regions_from_fd ( struct dm_stats * dms , int fd ,
int group , int precise ,
struct dm_histogram * bounds ,
const char * alias )
{
log_error ( " File mapping requires FIEMAP ioctl support. " ) ;
return 0 ;
}
# endif /* HAVE_LINUX_FIEMAP */
2015-08-19 22:39:10 +03:00
/*
* Backward compatible dm_stats_create_region ( ) implementations .
*
* Keep these at the end of the file to avoid adding clutter around the
* current dm_stats_create_region ( ) version .
*/
# if defined(__GNUC__)
int dm_stats_create_region_v1_02_106 ( struct dm_stats * dms , uint64_t * region_id ,
uint64_t start , uint64_t len , int64_t step ,
int precise , const char * program_id ,
const char * aux_data ) ;
int dm_stats_create_region_v1_02_106 ( struct dm_stats * dms , uint64_t * region_id ,
uint64_t start , uint64_t len , int64_t step ,
int precise , const char * program_id ,
const char * aux_data )
{
/* 1.02.106 lacks histogram argument. */
return _stats_create_region ( dms , region_id , start , len , step , precise ,
NULL , program_id , aux_data ) ;
}
DM_EXPORT_SYMBOL ( dm_stats_create_region , 1 _02_106 ) ;
int dm_stats_create_region_v1_02_104 ( struct dm_stats * dms , uint64_t * region_id ,
uint64_t start , uint64_t len , int64_t step ,
const char * program_id , const char * aux_data ) ;
int dm_stats_create_region_v1_02_104 ( struct dm_stats * dms , uint64_t * region_id ,
uint64_t start , uint64_t len , int64_t step ,
const char * program_id , const char * aux_data )
{
/* 1.02.104 lacks histogram and precise arguments. */
return _stats_create_region ( dms , region_id , start , len , step , 0 , NULL ,
program_id , aux_data ) ;
}
DM_EXPORT_SYMBOL ( dm_stats_create_region , 1 _02_104 ) ;
# endif