blob: 820337631df9b1fcf50eff817220cbc4fd77af6f [file] [log] [blame] [raw]
/*
htop - freebsd/FreeBSDDiskList.c
Copyright 2015-2024 Rivoreo
Released under the GNU GPL, see the COPYING file
in the source distribution for its full text.
*/
/*{
#include "DiskList.h"
#include <libgeom.h>
#include <time.h>
typedef struct {
DiskList super;
void *previous_stats;
struct gmesh tree;
time_t tree_mtime;
} FreeBSDDiskList;
}*/
#include "config.h"
#include "FreeBSDDiskList.h"
#include "FreeBSDDisk.h"
#include "CRT.h"
#include "XAlloc.h"
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/disk.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/devicestat.h>
#include <devstat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <ctype.h>
#include <cam/cam.h>
#include <cam/cam_ccb.h>
#include <cam/scsi/scsi_pass.h>
#include <assert.h>
DiskList *DiskList_new(const Settings *settings) {
int e = geom_stats_open();
if(e && e != EBUSY) CRT_fatalError("geom_stats_open", e);
FreeBSDDiskList *disk_list = xMalloc(sizeof(FreeBSDDiskList));
DiskList_init(&disk_list->super, Class(FreeBSDDisk), settings);
disk_list->previous_stats = geom_stats_snapshot_get();
if(!disk_list->previous_stats) CRT_fatalError("geom_stats_snapshot_get", 0);
disk_list->tree_mtime = -1;
return (DiskList *)disk_list;
}
void DiskList_delete(DiskList *super) {
DiskList_done(super);
FreeBSDDiskList *this = (FreeBSDDiskList *)super;
geom_stats_snapshot_free(this->previous_stats);
free(this);
}
static void fill_from_device_node(Disk *disk, int flags, bool need_block_size) {
size_t len = strlen(disk->name) + 1;
char path[5 + len];
memcpy(path, "/dev/", 5);
memcpy(path + 5, disk->name, len);
int fd = open(path, O_RDONLY);
if(fd == -1) return;
if(need_block_size) {
unsigned int size;
if(ioctl(fd, DIOCGSECTORSIZE, &size) == 0 && size) disk->block_size = size;
}
#ifdef DIOCGPHYSPATH
if(flags & HTOP_DISK_PHYS_PATH_FLAG) {
char phys_path[MAXPATHLEN];
if(ioctl(fd, DIOCGPHYSPATH, phys_path) == 0) disk->phys_path = xStrdup(phys_path);
}
#endif
if(flags & HTOP_DISK_CAPACITY_FLAG) {
off_t size;
if(ioctl(fd, DIOCGMEDIASIZE, &size) == 0) {
disk->block_count = size / disk->block_size;
} else {
size = lseek(fd, 0, SEEK_END);
if(size >= 0) disk->block_count = size / disk->block_size;
}
}
close(fd);
}
static char *get_cam_path(const char *name) {
size_t name_len = strlen(name);
size_t unit_i = name_len;
while(isdigit(name[unit_i - 1])) if(!--unit_i) return NULL;
if(unit_i == name_len) return NULL;
int fd = open("/dev/xpt0", O_RDWR);
if(fd == -1) return NULL;
struct dev_match_pattern match_pattern = {
.type = DEV_MATCH_PERIPH,
.pattern.periph_pattern.flags = PERIPH_MATCH_NAME | PERIPH_MATCH_UNIT,
.pattern.periph_pattern.unit_number = atoi(name + unit_i)
};
assert(sizeof match_pattern.pattern.periph_pattern.periph_name > unit_i);
memcpy(match_pattern.pattern.periph_pattern.periph_name, name, unit_i);
struct dev_match_result match_result;
union ccb ccb = {
.cdm = {
.ccb_h.path_id = CAM_XPT_PATH_ID,
.ccb_h.target_id = CAM_TARGET_WILDCARD,
.ccb_h.target_lun = CAM_LUN_WILDCARD,
.ccb_h.func_code = XPT_DEV_MATCH,
.num_patterns = 1,
.pattern_buf_len = sizeof match_pattern,
.patterns = &match_pattern,
.num_matches = 0,
.match_buf_len = sizeof match_result,
.matches = &match_result
}
};
if(ioctl(fd, CAMIOCOMMAND, &ccb) < 0) {
close(fd);
return NULL;
}
close(fd);
if(ccb.ccb_h.status != CAM_REQ_CMP) return NULL;
if(ccb.cdm.status != CAM_DEV_MATCH_LAST && ccb.cdm.status != CAM_DEV_MATCH_MORE) {
return NULL;
}
/*
assert(ccb.cdm.num_matches <= sizeof match_results / sizeof *match_results);
for(int i = 0; i < ccb.cdm.num_matches; i++) {
const struct dev_match_result *match = ccb.cdm.matches + i;
assert(match->type == DEV_MATCH_PERIPH);
*/
assert(ccb.cdm.num_matches <= 1);
if(!ccb.cdm.num_matches) return NULL;
assert(ccb.cdm.matches->type == DEV_MATCH_PERIPH);
char *phys_path = xMalloc(41);
int len = snprintf(phys_path, 41, "scbus%u-target%u-lun%u",
(unsigned int)ccb.cdm.matches->result.periph_result.path_id,
(unsigned int)ccb.cdm.matches->result.periph_result.target_id,
(unsigned int)ccb.cdm.matches->result.periph_result.target_lun);
if(len < 40) phys_path = xRealloc(phys_path, len + 1);
return phys_path;
}
#define BINTIME_TO_HUNDREDTHSEC(BINTIME) \
((BINTIME)->sec * 100 + (((uint8_t)((BINTIME)->frac>>48) * (uint8_t)100) >> 8))
void DiskList_internalScan(DiskList *super, double unused_interval) {
FreeBSDDiskList *this = (FreeBSDDiskList *)super;
void *stats = geom_stats_snapshot_get();
if(!stats) return;
struct timespec ts, prev_ts;
geom_stats_snapshot_timestamp(stats, &ts);
geom_stats_snapshot_timestamp(this->previous_stats, &prev_ts);
long double interval = (ts.tv_sec - prev_ts.tv_sec) +
(long double)(ts.tv_nsec - prev_ts.tv_nsec) / 1000000000;
if(this->tree_mtime == (time_t)-1 || ts.tv_sec - this->tree_mtime > 60) {
int e = geom_gettree(&this->tree);
if(e) CRT_fatalError("geom_gettree", e);
this->tree_mtime = ts.tv_sec;
}
struct devstat *devstat;
while((devstat = geom_stats_snapshot_next(stats))) {
struct devstat *prev_devstat = geom_stats_snapshot_next(this->previous_stats);
const struct gident *gid = geom_lookupid(&this->tree, devstat->id);
if(!gid) continue;
if(gid->lg_what != ISPROVIDER) continue;
const struct gprovider *p = gid->lg_ptr;
if(p->lg_geom->lg_rank != 1) continue;
bool is_existing;
Disk *disk = DiskList_getOrCreate(super, p->lg_name, &is_existing, (DiskConstructor)FreeBSDDisk_new);
FreeBSDDisk *fbsd_disk = (FreeBSDDisk *)disk;
if(!is_existing) {
//disk->block_size = lg_sectorsize;
disk->block_size = devstat->block_size ? : 512;
disk->creation_time = devstat->creation_time.sec;
if(!devstat->block_size ||
(super->settings->disk_flags & (HTOP_DISK_PHYS_PATH_FLAG | HTOP_DISK_CAPACITY_FLAG))) {
fill_from_device_node(disk, super->settings->disk_flags, !devstat->block_size);
}
if(super->settings->disk_flags & HTOP_DISK_DEVID_FLAG) {
struct gconfig *kvp;
LIST_FOREACH(kvp, &p->lg_config, lg_config) {
if(kvp->lg_val && strcmp(kvp->lg_name, "ident") == 0) {
disk->devid = xStrdup(kvp->lg_val);
break;
}
}
}
if(super->settings->disk_flags & HTOP_DISK_CAM_PATH_FLAG) {
fbsd_disk->cam_path = get_cam_path(p->lg_name);
}
DiskList_add(super, disk);
}
struct timeval duration_tv;
#ifdef DSM_TOTAL_DURATION
long double total_duration;
#endif
#ifdef DSM_TOTAL_BUSY_TIME
long double total_busy_time;
#endif
devstat_compute_statistics(devstat, NULL, 0,
DSM_QUEUE_LENGTH, &disk->queue_length,
DSM_TOTAL_TRANSFERS, &disk->operation_count,
DSM_TOTAL_TRANSFERS_READ, &disk->read_operation_count,
DSM_TOTAL_TRANSFERS_WRITE, &disk->write_operation_count,
DSM_TOTAL_BLOCKS_READ, &disk->read_block_count,
DSM_TOTAL_BLOCKS_WRITE, &disk->write_block_count,
#ifdef DSM_TOTAL_DURATION
DSM_TOTAL_DURATION, &total_duration,
#endif
#ifdef DSM_TOTAL_BUSY_TIME
DSM_TOTAL_BUSY_TIME, &total_busy_time,
#endif
DSM_NONE);
#ifdef DSM_TOTAL_DURATION
duration_tv.tv_sec = total_duration;
duration_tv.tv_usec = (int)(total_duration * 1000000) % 1000000;
disk->oper_time = total_duration * 100;
#else
struct bintime duration = devstat->duration[DEVSTAT_READ];
bintime_add(&duration, devstat->duration + DEVSTAT_WRITE);
bintime_add(&duration, devstat->duration + DEVSTAT_FREE);
bintime_add(&duration, devstat->duration + DEVSTAT_NO_DATA);
bintime2timeval(&duration, &duration_tv);
disk->oper_time = BINTIME_TO_HUNDREDTHSEC(&duration);
#endif
#ifdef DSM_TOTAL_BUSY_TIME
fbsd_disk->busy_time = total_busy_time * 100;
#else
fbsd_disk->busy_time = BINTIME_TO_HUNDREDTHSEC(&devstat->busy_time);
#endif
if(super->settings->disk_flags & HTOP_DISK_PERCENT_UTIL_FLAG) {
struct timeval delta;
if(timercmp(&duration_tv, &fbsd_disk->duration, >)) {
timersub(&duration_tv, &fbsd_disk->duration, &delta);
} else {
delta.tv_sec = 0;
delta.tv_usec = 0;
}
disk->percent_util =
(delta.tv_sec * 100 + (long double)delta.tv_usec / 10000) /
interval;
if(disk->percent_util > 100) disk->percent_util = 100;
}
fbsd_disk->duration = duration_tv;
if(prev_devstat) {
long double transfers_per_second;
long double transfers_per_second_read;
long double transfers_per_second_write;
long double blocks_per_seconds_read;
long double blocks_per_seconds_write;
long double busy_pct;
assert(devstat->id == prev_devstat->id);
devstat_compute_statistics(devstat, prev_devstat, interval,
DSM_TRANSFERS_PER_SECOND, &transfers_per_second,
DSM_TRANSFERS_PER_SECOND_READ, &transfers_per_second_read,
DSM_TRANSFERS_PER_SECOND_WRITE, &transfers_per_second_write,
DSM_BLOCKS_PER_SECOND_READ, &blocks_per_seconds_read,
DSM_BLOCKS_PER_SECOND_WRITE, &blocks_per_seconds_write,
DSM_BUSY_PCT, &busy_pct,
DSM_NONE);
disk->operation_rate = transfers_per_second;
disk->read_operation_rate = transfers_per_second_read;
disk->write_operation_rate = transfers_per_second_write;
disk->read_block_rate = blocks_per_seconds_read;
disk->write_block_rate = blocks_per_seconds_write;
fbsd_disk->percent_busy = busy_pct;
}
disk->updated = true;
}
geom_stats_snapshot_reset(stats);
geom_stats_snapshot_free(this->previous_stats);
this->previous_stats = stats;
}