1
0
mirror of git://sourceware.org/git/lvm2.git synced 2024-10-29 21:55:56 +03:00
lvm2/libdm/libdm-stats.c
Bryn M. Reeves f4262026b6 libdm: add precise timestamps support to libdm-stats
Add support for the kernel precise_timestamps feature. This allows
regions to be created using counters with nanosecond precision.

A new dm_stats method, dm_stats_set_precise_timestamps() causes all
future regions created with this handle to attempt to enable precise
counters.
2015-08-24 20:03:21 +01:00

1492 lines
36 KiB
C

/*
* Copyright (C) 2015 Red Hat, Inc. All rights reserved.
*
* 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,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "dmlib.h"
#define DM_STATS_REGION_NOT_PRESENT UINT64_MAX
#define NSEC_PER_MSEC 1000000L
#define NSEC_PER_SEC 1000000000L
#define PRECISE_ARG "precise_timestamps"
/*
* 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 */
};
struct dm_stats_region {
uint64_t region_id; /* as returned by @stats_list */
uint64_t start;
uint64_t len;
uint64_t step;
char *program_id;
char *aux_data;
uint64_t timescale; /* precise_timestamps is per-region */
struct dm_stats_counters *counters;
};
struct dm_stats {
/* device binding */
int major; /* device major that this dm_stats object is bound to */
int minor; /* device minor that this dm_stats object is bound to */
char *name; /* device-mapper device name */
char *uuid; /* device-mapper UUID */
char *program_id; /* default program_id for this handle */
struct dm_pool *mem; /* memory pool for region and counter tables */
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 */
uint64_t timescale; /* default sample value multiplier */
int precise; /* use precise_timestamps when creating regions */
struct dm_stats_region *regions;
/* statistics cursor */
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;
char buf[256];
if (!(comm = fopen(PROC_SELF_COMM, "r")))
return_NULL;
if (!fgets(buf, sizeof(buf), comm)) {
log_error("Could not read from %s", PROC_SELF_COMM);
if(fclose(comm))
stack;
return NULL;
}
if (fclose(comm))
stack;
return dm_strdup(buf);
}
struct dm_stats *dm_stats_create(const char *program_id)
{
struct dm_stats *dms = NULL;
if (!(dms = dm_zalloc(sizeof(*dms))))
return_NULL;
if (!(dms->mem = dm_pool_create("stats_pool", 4096)))
goto_out;
if (!program_id || !strlen(program_id))
dms->program_id = _program_id_from_proc();
else
dms->program_id = dm_strdup(program_id);
dms->major = -1;
dms->minor = -1;
dms->name = NULL;
dms->uuid = NULL;
/* by default all regions use msec precision */
dms->timescale = NSEC_PER_MSEC;
dms->precise = 0;
dms->nr_regions = DM_STATS_REGION_NOT_PRESENT;
dms->max_region = DM_STATS_REGION_NOT_PRESENT;
dms->regions = NULL;
return dms;
out:
dm_free(dms);
return NULL;
}
/**
* 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);
}
static void _stats_region_destroy(struct dm_stats_region *region)
{
if (!_stats_region_present(region))
return;
/**
* Don't free counters here explicitly; it will be dropped
* from the pool along with the corresponding regions table.
*/
if (region->program_id)
dm_free(region->program_id);
if (region->aux_data)
dm_free(region->aux_data);
}
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 */
for (i = dms->max_region; (i != DM_STATS_REGION_NOT_PRESENT); i--)
_stats_region_destroy(&dms->regions[i]);
dm_pool_free(mem, dms->regions);
}
static int _set_stats_device(struct dm_stats *dms, struct dm_task *dmt)
{
if (dms->name)
return dm_task_set_name(dmt, dms->name);
if (dms->uuid)
return dm_task_set_uuid(dmt, dms->uuid);
if (dms->major > 0)
return dm_task_set_major(dmt, dms->major)
&& dm_task_set_minor(dmt, dms->minor);
return_0;
}
static int _stats_bound(struct dm_stats *dms)
{
if (dms->major > 0 || dms->name || dms->uuid)
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)
{
if (dms->name)
dm_pool_free(dms->mem, dms->name);
if (dms->uuid)
dm_pool_free(dms->mem, dms->uuid);
dms->name = dms->uuid = NULL;
dms->major = dms->minor = -1;
}
int dm_stats_bind_devno(struct dm_stats *dms, int major, int minor)
{
_stats_clear_binding(dms);
_stats_regions_destroy(dms);
dms->major = major;
dms->minor = minor;
return 1;
}
int dm_stats_bind_name(struct dm_stats *dms, const char *name)
{
_stats_clear_binding(dms);
_stats_regions_destroy(dms);
if (!(dms->name = dm_pool_strdup(dms->mem, name)))
return_0;
return 1;
}
int dm_stats_bind_uuid(struct dm_stats *dms, const char *uuid)
{
_stats_clear_binding(dms);
_stats_regions_destroy(dms);
if (!(dms->uuid = dm_pool_strdup(dms->mem, uuid)))
return_0;
return 1;
}
static int _stats_check_precise_timestamps(const struct dm_stats *dms)
{
/* Already checked? */
if (dms && dms->precise)
return 1;
return dm_message_supports_precise_timestamps();
}
int dm_stats_driver_supports_precise(void)
{
return _stats_check_precise_timestamps(NULL);
}
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))
goto_out;
if (!dm_task_set_message(dmt, msg))
goto_out;
if (!dm_task_run(dmt))
goto_out;
return dmt;
out:
dm_task_destroy(dmt);
return NULL;
}
static int _stats_parse_list_region(struct dm_stats_region *region, char *line)
{
char *p = NULL, string_data[4096]; /* FIXME: add dm_sscanf with %ms? */
const char *program_id, *aux_data, *stats_args;
const char *empty_string = "";
int r;
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.
*/
r = sscanf(line, FMTu64 ": " FMTu64 "+" FMTu64 " " FMTu64 " %4095c",
&region->region_id, &region->start, &region->len,
&region->step, string_data);
if (r != 5)
return_0;
/* 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';
if (!strcmp(program_id, "-"))
program_id = empty_string;
aux_data = p + 1;
if ((p = strchr(aux_data, ' '))) {
/* terminate aux_data string. */
*p = '\0';
if (!strcmp(aux_data, "-"))
aux_data = empty_string;
stats_args = p + 1;
} else
stats_args = empty_string;
} else
aux_data = stats_args = empty_string;
if (strstr(stats_args, PRECISE_ARG))
region->timescale = 1;
else
region->timescale = NSEC_PER_MSEC;
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)
{
struct dm_pool *mem = dms->mem;
struct dm_stats_region cur;
uint64_t max_region = 0, nr_regions = 0;
FILE *list_rows;
/* FIXME: determine correct maximum line length based on kernel format */
char line[256];
if (!resp) {
log_error("Could not parse NULL @stats_list response.");
return 0;
}
if (dms->regions)
_stats_regions_destroy(dms);
/* 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;
if (!dm_pool_begin_object(mem, 1024))
goto_out;
while(fgets(line, sizeof(line), list_rows)) {
if (!_stats_parse_list_region(&cur, line))
goto_out;
/* handle holes in the list of region_ids */
if (cur.region_id > max_region) {
struct dm_stats_region fill;
memset(&fill, 0, sizeof(fill));
fill.region_id = DM_STATS_REGION_NOT_PRESENT;
do {
if (!dm_pool_grow_object(mem, &fill, sizeof(fill)))
goto_out;
} while (max_region++ < (cur.region_id - 1));
}
if (!dm_pool_grow_object(mem, &cur, sizeof(cur)))
goto_out;
max_region++;
nr_regions++;
}
dms->nr_regions = nr_regions;
dms->max_region = max_region - 1;
dms->regions = dm_pool_end_object(mem);
if (fclose(list_rows))
stack;
return 1;
out:
if(fclose(list_rows))
stack;
dm_pool_abandon_object(mem);
return 0;
}
int dm_stats_list(struct dm_stats *dms, const char *program_id)
{
struct dm_task *dmt;
char msg[256];
int r;
if (!_stats_bound(dms))
return_0;
/* allow zero-length program_id for list */
if (!program_id)
program_id = dms->program_id;
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)))
return 0;
if (!_stats_parse_list(dms, dm_task_get_message_response(dmt))) {
log_error("Could not parse @stats_list response.");
goto out;
}
dm_task_destroy(dmt);
return 1;
out:
dm_task_destroy(dmt);
return 0;
}
static int _stats_parse_region(struct dm_pool *mem, const char *resp,
struct dm_stats_region *region,
uint64_t timescale)
{
struct dm_stats_counters cur;
FILE *stats_rows = NULL;
uint64_t start, len;
char row[256];
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))
goto_out;
/*
* 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)
goto_out;
/*
* 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.");
goto out;
}
/* 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;
}
if(!dm_pool_grow_object(mem, &cur, sizeof(cur)))
goto_out;
if (region->start == UINT64_MAX) {
region->start = start;
region->step = len; /* area size is always uniform. */
}
}
region->len = (start + len) - region->start;
region->timescale = timescale;
region->counters = dm_pool_end_object(mem);
if (fclose(stats_rows))
stack;
return 1;
out:
if (stats_rows)
if(fclose(stats_rows))
stack;
dm_pool_abandon_object(mem);
return 0;
}
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);
}
static void _stats_walk_next(const struct dm_stats *dms, int region,
uint64_t *cur_r, uint64_t *cur_a)
{
struct dm_stats_region *cur = NULL;
int present;
if (!dms || !dms->regions)
return;
cur = &dms->regions[*cur_r];
present = _stats_region_present(cur);
if (region && present)
*cur_a = _nr_areas_region(cur);
if (region || !present || ++(*cur_a) == _nr_areas_region(cur)) {
*cur_a = 0;
while(!dm_stats_region_present(dms, ++(*cur_r))
&& *cur_r < dms->max_region)
; /* keep walking until a present region is found
* or the end of the table is reached. */
}
}
static void _stats_walk_start(const struct dm_stats *dms,
uint64_t *cur_r, uint64_t *cur_a)
{
if (!dms || !dms->regions)
return;
*cur_r = 0;
*cur_a = 0;
/* advance to the first present region */
if (!dm_stats_region_present(dms, dms->cur_region))
_stats_walk_next(dms, 0, cur_r, cur_a);
}
void dm_stats_walk_start(struct dm_stats *dms)
{
_stats_walk_start(dms, &dms->cur_region, &dms->cur_area);
}
void dm_stats_walk_next(struct dm_stats *dms)
{
_stats_walk_next(dms, 0, &dms->cur_region, &dms->cur_area);
}
void dm_stats_walk_next_region(struct dm_stats *dms)
{
_stats_walk_next(dms, 1, &dms->cur_region, &dms->cur_area);
}
static int _stats_walk_end(const struct dm_stats *dms,
uint64_t *cur_r, uint64_t *cur_a)
{
struct dm_stats_region *region = NULL;
int end = 0;
if (!dms || !dms->regions)
return 1;
region = &dms->regions[*cur_r];
end = (*cur_r > dms->max_region
|| (*cur_r == dms->max_region
&& *cur_a >= _nr_areas_region(region)));
return end;
}
int dm_stats_walk_end(struct dm_stats *dms)
{
return _stats_walk_end(dms, &dms->cur_region, &dms->cur_area);
}
uint64_t dm_stats_get_region_nr_areas(const struct dm_stats *dms,
uint64_t region_id)
{
struct dm_stats_region *region = &dms->regions[region_id];
return _nr_areas_region(region);
}
uint64_t dm_stats_get_current_nr_areas(const struct dm_stats *dms)
{
return dm_stats_get_region_nr_areas(dms, dms->cur_region);
}
uint64_t dm_stats_get_nr_areas(const struct dm_stats *dms)
{
uint64_t nr_areas = 0;
/* use a separate cursor */
uint64_t cur_region, cur_area;
_stats_walk_start(dms, &cur_region, &cur_area);
do {
nr_areas += dm_stats_get_current_nr_areas(dms);
_stats_walk_next(dms, 1, &cur_region, &cur_area);
} while (!_stats_walk_end(dms, &cur_region, &cur_area));
return nr_areas;
}
static int _stats_create_region(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)
{
struct dm_task *dmt = NULL;
char msg[1024], range[64];
const char *err_fmt = "Could not prepare @stats_create %s.";
const char *precise_str = PRECISE_ARG;
const char *resp, *opt_args = NULL;
int nr_opt = 0, r = 0; /* number of optional args. */
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");
return 0;
}
}
if (precise < 0)
precise = dms->precise;
if (precise)
nr_opt++;
else
precise_str = "";
if (nr_opt) {
if ((dm_asprintf((char **)&opt_args, "%d %s", nr_opt,
precise_str)) < 0) {
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 : "-",
(step < 0) ? "/" : "",
(uint64_t)llabs(step),
opt_args, program_id, aux_data)) {
log_error(err_fmt, "message");
dm_free((void *) opt_args);
return 0;
}
if (!(dmt = _stats_send_message(dms, msg)))
goto out;
resp = dm_task_get_message_response(dmt);
if (!resp) {
log_error("Could not parse empty @stats_create response.");
goto out;
}
if (region_id) {
char *endptr = NULL;
*region_id = strtoull(resp, &endptr, 10);
if (resp == endptr)
goto_out;
}
r = 1;
out:
if(dmt)
dm_task_destroy(dmt);
dm_free((void *) opt_args);
return r;
}
int dm_stats_create_region(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)
{
/* Nanosecond counters need precise_timestamps. */
if (precise && !_stats_check_precise_timestamps(dms))
return 0;
return _stats_create_region(dms, region_id, start, len, step,
precise,
program_id, aux_data);
}
/* Backward compatible dm_stats_create_region() */
#if defined(__GNUC__)
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)
{
return _stats_create_region(dms, region_id, start, len, step,
dms->precise,
program_id, aux_data);
}
DM_EXPORT_SYMBOL(dm_stats_create_region, 1_02_104);
#endif
int dm_stats_delete_region(struct dm_stats *dms, uint64_t region_id)
{
struct dm_task *dmt;
char msg[1024];
if (!_stats_bound(dms))
return_0;
if (!dm_snprintf(msg, sizeof(msg), "@stats_delete " FMTu64, region_id)) {
log_error("Could not prepare @stats_delete message.");
goto out;
}
dmt = _stats_send_message(dms, msg);
if (!dmt)
goto_out;
dm_task_destroy(dmt);
return 1;
out:
return 0;
}
int dm_stats_clear_region(struct dm_stats *dms, uint64_t region_id)
{
struct dm_task *dmt;
char msg[1024];
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.");
goto out;
}
dmt = _stats_send_message(dms, msg);
if (!dmt)
goto_out;
dm_task_destroy(dmt);
return 1;
out:
return 0;
}
static struct dm_task *_stats_print_region(struct dm_stats *dms,
uint64_t region_id, unsigned start_line,
unsigned num_lines, unsigned clear)
{
struct dm_task *dmt = NULL;
/* @stats_print[_clear] <region_id> [<start_line> <num_lines>] */
const char *clear_str = "_clear", *lines_fmt = "%u %u";
const char *msg_fmt = "@stats_print%s " FMTu64 " %s";
const char *err_fmt = "Could not prepare @stats_print %s.";
char msg[1024], lines[64];
if (start_line || num_lines)
if (!dm_snprintf(lines, sizeof(lines),
lines_fmt, start_line, num_lines)) {
log_error(err_fmt, "row specification");
goto out;
}
if (!dm_snprintf(msg, sizeof(msg), msg_fmt, (clear) ? clear_str : "",
region_id, (start_line || num_lines) ? lines : "")) {
log_error(err_fmt, "message");
goto out;
}
if (!(dmt = _stats_send_message(dms, msg)))
goto out;
return dmt;
out:
return NULL;
}
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;
if (!_stats_bound(dms))
return_0;
dmt = _stats_print_region(dms, region_id,
start_line, num_lines, clear);
if (!dmt)
return 0;
resp = dm_pool_strdup(dms->mem, dm_task_get_message_response(dmt));
dm_task_destroy(dmt);
if (!resp)
log_error("Could not allocate memory for response buffer.");
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)
{
if (!dms || !dms->regions)
return 0;
return dms->nr_regions;
}
/**
* Test whether region_id is present in this set of stats data
*/
int dm_stats_region_present(const struct dm_stats *dms, uint64_t region_id)
{
if (!dms->regions)
return 0;
if (region_id > dms->max_region)
return 0;
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;
if (!_stats_parse_region(dms->mem, resp, region, region->timescale)) {
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);
if (!_stats_bound(dms))
return_0;
/* 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.");
goto out;
}
/* successful list but no regions registered */
if (!dms->nr_regions)
return 0;
dm_stats_walk_start(dms);
do {
struct dm_task *dmt = NULL; /* @stats_print task */
const char *resp;
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)))
goto_out;
resp = dm_task_get_message_response(dmt);
if (!_dm_stats_populate_region(dms, region_id, resp)) {
dm_task_destroy(dmt);
goto_out;
}
dm_task_destroy(dmt);
dm_stats_walk_next_region(dms);
} while (all_regions && !dm_stats_walk_end(dms));
return 1;
out:
_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);
_stats_clear_binding(dms);
dm_pool_destroy(dms->mem);
dm_free(dms->program_id);
dm_free(dms);
}
/**
* Methods for accessing counter fields. All methods share the
* following naming scheme and prototype:
*
* uint64_t dm_stats_get_COUNTER(struct dm_stats *, uint64_t, uint64_t)
*
* Where the two integer arguments are the region_id and area_id
* respectively.
*/
#define MK_STATS_GET_COUNTER_FN(counter) \
uint64_t dm_stats_get_ ## counter(const struct dm_stats *dms, \
uint64_t region_id, uint64_t area_id) \
{ \
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 ; \
return dms->regions[region_id].counters[area_id].counter; \
}
MK_STATS_GET_COUNTER_FN(reads)
MK_STATS_GET_COUNTER_FN(reads_merged)
MK_STATS_GET_COUNTER_FN(read_sectors)
MK_STATS_GET_COUNTER_FN(read_nsecs)
MK_STATS_GET_COUNTER_FN(writes)
MK_STATS_GET_COUNTER_FN(writes_merged)
MK_STATS_GET_COUNTER_FN(write_sectors)
MK_STATS_GET_COUNTER_FN(write_nsecs)
MK_STATS_GET_COUNTER_FN(io_in_progress)
MK_STATS_GET_COUNTER_FN(io_nsecs)
MK_STATS_GET_COUNTER_FN(weighted_io_nsecs)
MK_STATS_GET_COUNTER_FN(total_read_nsecs)
MK_STATS_GET_COUNTER_FN(total_write_nsecs)
#undef MK_STATS_GET_COUNTER_FN
int dm_stats_get_rd_merges_per_sec(const struct dm_stats *dms, double *rrqm,
uint64_t region_id, uint64_t area_id)
{
struct dm_stats_counters *c;
if (!dms->interval_ns)
return_0;
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 ;
c = &(dms->regions[region_id].counters[area_id]);
*rrqm = ((double) c->reads_merged) / (double) dms->interval_ns;
return 1;
}
int dm_stats_get_wr_merges_per_sec(const struct dm_stats *dms, double *wrqm,
uint64_t region_id, uint64_t area_id)
{
struct dm_stats_counters *c;
if (!dms->interval_ns)
return_0;
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 ;
c = &(dms->regions[region_id].counters[area_id]);
*wrqm = ((double) c->writes_merged) / (double) dms->interval_ns;
return 1;
}
int dm_stats_get_reads_per_sec(const struct dm_stats *dms, double *rd_s,
uint64_t region_id, uint64_t area_id)
{
struct dm_stats_counters *c;
if (!dms->interval_ns)
return_0;
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 ;
c = &(dms->regions[region_id].counters[area_id]);
*rd_s = ((double) c->reads * NSEC_PER_SEC) / (double) dms->interval_ns;
return 1;
}
int dm_stats_get_writes_per_sec(const struct dm_stats *dms, double *wr_s,
uint64_t region_id, uint64_t area_id)
{
struct dm_stats_counters *c;
if (!dms->interval_ns)
return_0;
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 ;
c = &(dms->regions[region_id].counters[area_id]);
*wr_s = ((double) c->writes * (double) NSEC_PER_SEC)
/ (double) dms->interval_ns;
return 1;
}
int dm_stats_get_read_sectors_per_sec(const struct dm_stats *dms, double *rsec_s,
uint64_t region_id, uint64_t area_id)
{
struct dm_stats_counters *c;
if (!dms->interval_ns)
return_0;
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 ;
c = &(dms->regions[region_id].counters[area_id]);
*rsec_s = ((double) c->read_sectors * (double) NSEC_PER_SEC)
/ (double) dms->interval_ns;
return 1;
}
int dm_stats_get_write_sectors_per_sec(const struct dm_stats *dms, double *wsec_s,
uint64_t region_id, uint64_t area_id)
{
struct dm_stats_counters *c;
if (!dms->interval_ns)
return_0;
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 ;
c = &(dms->regions[region_id].counters[area_id]);
*wsec_s = ((double) c->write_sectors * (double) NSEC_PER_SEC)
/ (double) dms->interval_ns;
return 1;
}
int dm_stats_get_average_request_size(const struct dm_stats *dms, double *arqsz,
uint64_t region_id, uint64_t area_id)
{
struct dm_stats_counters *c;
uint64_t nr_ios, nr_sectors;
if (!dms->interval_ns)
return_0;
*arqsz = 0.0;
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 ;
c = &(dms->regions[region_id].counters[area_id]);
nr_ios = c->reads + c->writes;
nr_sectors = c->read_sectors + c->write_sectors;
if (nr_ios)
*arqsz = (double) nr_sectors / (double) nr_ios;
return 1;
}
int dm_stats_get_average_queue_size(const struct dm_stats *dms, double *qusz,
uint64_t region_id, uint64_t area_id)
{
struct dm_stats_counters *c;
uint64_t io_ticks;
if (!dms->interval_ns)
return_0;
*qusz = 0.0;
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 ;
c = &(dms->regions[region_id].counters[area_id]);
io_ticks = c->weighted_io_nsecs;
if (io_ticks)
*qusz = (double) io_ticks / (double) dms->interval_ns;
return 1;
}
int dm_stats_get_average_wait_time(const struct dm_stats *dms, double *await,
uint64_t region_id, uint64_t area_id)
{
struct dm_stats_counters *c;
uint64_t io_ticks, nr_ios;
if (!dms->interval_ns)
return_0;
*await = 0.0;
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 ;
c = &(dms->regions[region_id].counters[area_id]);
io_ticks = c->read_nsecs + c->write_nsecs;
nr_ios = c->reads + c->writes;
if (nr_ios)
*await = (double) io_ticks / (double) nr_ios;
return 1;
}
int dm_stats_get_average_rd_wait_time(const struct dm_stats *dms,
double *await, uint64_t region_id,
uint64_t area_id)
{
struct dm_stats_counters *c;
uint64_t rd_io_ticks, nr_rd_ios;
if (!dms->interval_ns)
return_0;
*await = 0.0;
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 ;
c = &(dms->regions[region_id].counters[area_id]);
rd_io_ticks = c->read_nsecs;
nr_rd_ios = c->reads;
if (rd_io_ticks)
*await = (double) rd_io_ticks / (double) nr_rd_ios;
return 1;
}
int dm_stats_get_average_wr_wait_time(const struct dm_stats *dms,
double *await, uint64_t region_id,
uint64_t area_id)
{
struct dm_stats_counters *c;
uint64_t wr_io_ticks, nr_wr_ios;
if (!dms->interval_ns)
return_0;
*await = 0.0;
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 ;
c = &(dms->regions[region_id].counters[area_id]);
wr_io_ticks = c->write_nsecs;
nr_wr_ios = c->writes;
if (wr_io_ticks && nr_wr_ios)
*await = (double) wr_io_ticks / (double) nr_wr_ios;
return 1;
}
int dm_stats_get_service_time(const struct dm_stats *dms, double *svctm,
uint64_t region_id, uint64_t area_id)
{
dm_percent_t util;
double tput;
if (!dm_stats_get_throughput(dms, &tput, region_id, area_id))
return 0;
if (!dm_stats_get_utilization(dms, &util, region_id, area_id))
return 0;
/* avoid NAN with zero counter values */
if ( (uint64_t) tput == 0 || (uint64_t) util == 0) {
*svctm = 0.0;
return 1;
}
*svctm = ((double) NSEC_PER_SEC * dm_percent_to_float(util))
/ (100.0 * tput);
return 1;
}
int dm_stats_get_throughput(const struct dm_stats *dms, double *tput,
uint64_t region_id, uint64_t area_id)
{
struct dm_stats_counters *c;
if (!dms->interval_ns)
return_0;
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 ;
c = &(dms->regions[region_id].counters[area_id]);
*tput = (( NSEC_PER_SEC * ((double) c->reads + (double) c->writes))
/ (double) (dms->interval_ns));
return 1;
}
int dm_stats_get_utilization(const struct dm_stats *dms, dm_percent_t *util,
uint64_t region_id, uint64_t area_id)
{
struct dm_stats_counters *c;
uint64_t io_nsecs;
if (!dms->interval_ns)
return_0;
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 ;
c = &(dms->regions[region_id].counters[area_id]);
/**
* 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.
*/
io_nsecs = (c->io_nsecs <= dms->interval_ns) ? c->io_nsecs : dms->interval_ns;
*util = dm_make_percent(io_nsecs, dms->interval_ns);
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)
{
return dms->cur_area;
}
int dm_stats_get_region_start(const struct dm_stats *dms, uint64_t *start,
uint64_t region_id)
{
if (!dms || !dms->regions)
return_0;
*start = dms->regions[region_id].start;
return 1;
}
int dm_stats_get_region_len(const struct dm_stats *dms, uint64_t *len,
uint64_t region_id)
{
if (!dms || !dms->regions)
return_0;
*len = dms->regions[region_id].len;
return 1;
}
int dm_stats_get_region_area_len(const struct dm_stats *dms, uint64_t *len,
uint64_t region_id)
{
if (!dms || !dms->regions)
return_0;
*len = dms->regions[region_id].step;
return 1;
}
int dm_stats_get_current_region_start(const struct dm_stats *dms,
uint64_t *start)
{
return dm_stats_get_region_start(dms, start, dms->cur_region);
}
int dm_stats_get_current_region_len(const struct dm_stats *dms,
uint64_t *len)
{
return dm_stats_get_region_len(dms, len, dms->cur_region);
}
int dm_stats_get_current_region_area_len(const struct dm_stats *dms,
uint64_t *step)
{
return dm_stats_get_region_area_len(dms, step, dms->cur_region);
}
int dm_stats_get_area_start(const struct dm_stats *dms, uint64_t *start,
uint64_t region_id, uint64_t area_id)
{
struct dm_stats_region *region;
if (!dms || !dms->regions)
return_0;
region = &dms->regions[region_id];
*start = region->start + region->step * area_id;
return 1;
}
int dm_stats_get_area_offset(const struct dm_stats *dms, uint64_t *offset,
uint64_t region_id, uint64_t area_id)
{
if (!dms || !dms->regions)
return_0;
*offset = dms->regions[region_id].step * area_id;
return 1;
}
int dm_stats_get_current_area_start(const struct dm_stats *dms,
uint64_t *start)
{
return dm_stats_get_area_start(dms, start,
dms->cur_region, dms->cur_area);
}
int dm_stats_get_current_area_offset(const struct dm_stats *dms,
uint64_t *offset)
{
return dm_stats_get_area_offset(dms, offset,
dms->cur_region, dms->cur_area);
}
int dm_stats_get_current_area_len(const struct dm_stats *dms,
uint64_t *len)
{
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)
{
const char *program_id = dms->regions[region_id].program_id;
return (program_id) ? program_id : "";
}
const char *dm_stats_get_region_aux_data(const struct dm_stats *dms,
uint64_t region_id)
{
const char *aux_data = dms->regions[region_id].aux_data;
return (aux_data) ? aux_data : "" ;
}
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);
}