diff --git a/DiskIOMeter.c b/DiskIOMeter.c index 4df5c21b8..264074aa9 100644 --- a/DiskIOMeter.c +++ b/DiskIOMeter.c @@ -21,104 +21,174 @@ in the source distribution for its full text. #include "XUtils.h" -static const int DiskIOMeter_attributes[] = { - METER_VALUE_NOTICE, +typedef struct DiskIOMeterData_ { + Meter* diskIORateMeter; + Meter* diskIOTimeMeter; +} DiskIOMeterData; + +static const int DiskIORateMeter_attributes[] = { METER_VALUE_IOREAD, METER_VALUE_IOWRITE, }; +static const int DiskIOTimeMeter_attributes[] = { + METER_VALUE_NOTICE, +}; + static MeterRateStatus status = RATESTATUS_INIT; +static double cached_read_diff; static char cached_read_diff_str[6]; +static double cached_write_diff; static char cached_write_diff_str[6]; +static uint64_t cached_num_disks; static double cached_utilisation_diff; static double cached_utilisation_norm; -static void DiskIOMeter_updateValues(Meter* this) { - const Machine* host = this->host; - +static void DiskIOUpdateCache(const Machine* host) { static uint64_t cached_last_update; + uint64_t passedTimeInMs = host->realtimeMs - cached_last_update; - bool hasNewData = false; - DiskIOData data; /* update only every 500ms to have a sane span for rate calculation */ - if (passedTimeInMs > 500) { - hasNewData = Platform_getDiskIO(&data); - if (!hasNewData) { - status = RATESTATUS_NODATA; - } else if (cached_last_update == 0) { - status = RATESTATUS_INIT; - } else if (passedTimeInMs > 30000) { - status = RATESTATUS_STALE; - } else { - status = RATESTATUS_DATA; - } + if (passedTimeInMs <= 500) + return; - cached_last_update = host->realtimeMs; + DiskIOData data; + bool hasNewData = Platform_getDiskIO(&data); + if (!hasNewData) { + status = RATESTATUS_NODATA; + } else if (cached_last_update == 0) { + status = RATESTATUS_INIT; + } else if (passedTimeInMs > 30000) { + status = RATESTATUS_STALE; + } else { + status = RATESTATUS_DATA; } - if (hasNewData) { - static uint64_t cached_read_total; - static uint64_t cached_write_total; - static uint64_t cached_msTimeSpend_total; + cached_last_update = host->realtimeMs; - if (status != RATESTATUS_INIT) { - uint64_t diff; + if (!hasNewData) + return; - if (data.totalBytesRead > cached_read_total) { - diff = data.totalBytesRead - cached_read_total; - diff = (1000 * diff) / passedTimeInMs; /* convert to B/s */ - diff /= ONE_K; /* convert to KiB/s */ - } else { - diff = 0; - } - Meter_humanUnit(cached_read_diff_str, diff, sizeof(cached_read_diff_str)); - - if (data.totalBytesWritten > cached_write_total) { - diff = data.totalBytesWritten - cached_write_total; - diff = (1000 * diff) / passedTimeInMs; /* convert to B/s */ - diff /= ONE_K; /* convert to KiB/s */ - } else { - diff = 0; - } - Meter_humanUnit(cached_write_diff_str, diff, sizeof(cached_write_diff_str)); - - cached_utilisation_diff = 0.0; - cached_utilisation_norm = 0.0; - if (data.totalMsTimeSpend > cached_msTimeSpend_total) { - diff = data.totalMsTimeSpend - cached_msTimeSpend_total; - cached_utilisation_diff = 100.0 * (double)diff / passedTimeInMs; - if (data.numDisks > 0) { - cached_utilisation_norm = (double)diff / (passedTimeInMs * data.numDisks); - cached_utilisation_norm = MINIMUM(cached_utilisation_norm, 1.0); - } + static uint64_t cached_read_total; + static uint64_t cached_write_total; + static uint64_t cached_msTimeSpend_total; + + if (status != RATESTATUS_INIT) { + uint64_t diff; + + if (data.totalBytesRead > cached_read_total) { + diff = data.totalBytesRead - cached_read_total; + diff = (1000 * diff) / passedTimeInMs; /* convert to B/s */ + } else { + diff = 0; + } + cached_read_diff = diff; + Meter_humanUnit(cached_read_diff_str, cached_read_diff / ONE_K, sizeof(cached_read_diff_str)); + + if (data.totalBytesWritten > cached_write_total) { + diff = data.totalBytesWritten - cached_write_total; + diff = (1000 * diff) / passedTimeInMs; /* convert to B/s */ + } else { + diff = 0; + } + cached_write_diff = diff; + Meter_humanUnit(cached_write_diff_str, cached_write_diff / ONE_K, sizeof(cached_write_diff_str)); + + cached_num_disks = data.numDisks; + cached_utilisation_diff = 0.0; + cached_utilisation_norm = 0.0; + if (data.totalMsTimeSpend > cached_msTimeSpend_total) { + diff = data.totalMsTimeSpend - cached_msTimeSpend_total; + cached_utilisation_diff = 100.0 * (double)diff / passedTimeInMs; + if (data.numDisks > 0) { + cached_utilisation_norm = (double)diff / (passedTimeInMs * data.numDisks); + cached_utilisation_norm = MINIMUM(cached_utilisation_norm, 1.0); } } + } + + cached_read_total = data.totalBytesRead; + cached_write_total = data.totalBytesWritten; + cached_msTimeSpend_total = data.totalMsTimeSpend; +} + +static void DiskIORateMeter_updateValues(Meter* this) { + DiskIOUpdateCache(this->host); - cached_read_total = data.totalBytesRead; - cached_write_total = data.totalBytesWritten; - cached_msTimeSpend_total = data.totalMsTimeSpend; + this->values[0] = cached_read_diff; + this->values[1] = cached_write_diff; + + switch (status) { + case RATESTATUS_NODATA: + xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "no data"); + return; + case RATESTATUS_INIT: + xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "init"); + return; + case RATESTATUS_STALE: + xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "stale"); + return; + case RATESTATUS_DATA: + break; } - this->values[0] = cached_utilisation_norm; + xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "r:%siB/s w:%siB/s", cached_read_diff_str, cached_write_diff_str); +} - if (status == RATESTATUS_NODATA) { - xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "no data"); - return; +static void DiskIORateMeter_display(ATTR_UNUSED const Object* cast, RichString* out) { + switch (status) { + case RATESTATUS_NODATA: + RichString_writeAscii(out, CRT_colors[METER_VALUE_ERROR], "no data"); + return; + case RATESTATUS_INIT: + RichString_writeAscii(out, CRT_colors[METER_VALUE], "initializing..."); + return; + case RATESTATUS_STALE: + RichString_writeAscii(out, CRT_colors[METER_VALUE_WARN], "stale data"); + return; + case RATESTATUS_DATA: + break; } - if (status == RATESTATUS_INIT) { - xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "init"); - return; + + RichString_appendAscii(out, CRT_colors[METER_TEXT], "read: "); + RichString_appendAscii(out, CRT_colors[METER_VALUE_IOREAD], cached_read_diff_str); + RichString_appendAscii(out, CRT_colors[METER_VALUE_IOREAD], "iB/s"); + + RichString_appendAscii(out, CRT_colors[METER_TEXT], " write: "); + RichString_appendAscii(out, CRT_colors[METER_VALUE_IOWRITE], cached_write_diff_str); + RichString_appendAscii(out, CRT_colors[METER_VALUE_IOWRITE], "iB/s"); +} + +static void DiskIOTimeMeter_updateValues(Meter* this) { + DiskIOUpdateCache(this->host); + + this->values[0] = cached_utilisation_norm; + + switch (status) { + case RATESTATUS_NODATA: + xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "no data"); + return; + case RATESTATUS_INIT: + xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "init"); + return; + case RATESTATUS_STALE: + xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "stale"); + return; + case RATESTATUS_DATA: + break; } - if (status == RATESTATUS_STALE) { - xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "stale"); - return; + + char numDisksStr[12]; + numDisksStr[0] = '\0'; + if (cached_num_disks > 1 && cached_num_disks < 1000) { + xSnprintf(numDisksStr, sizeof(numDisksStr), " (%udisks)", (unsigned int)cached_num_disks); } - xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "r:%siB/s w:%siB/s %.1f%%", cached_read_diff_str, cached_write_diff_str, cached_utilisation_diff); + xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%.1f%%%s", cached_utilisation_diff, numDisksStr); } -static void DiskIOMeter_display(ATTR_UNUSED const Object* cast, RichString* out) { +static void DiskIOTimeMeter_display(ATTR_UNUSED const Object* cast, RichString* out) { switch (status) { case RATESTATUS_NODATA: RichString_writeAscii(out, CRT_colors[METER_VALUE_ERROR], "no data"); @@ -138,30 +208,154 @@ static void DiskIOMeter_display(ATTR_UNUSED const Object* cast, RichString* out) int color = cached_utilisation_diff > 40.0 ? METER_VALUE_NOTICE : METER_VALUE; int len = xSnprintf(buffer, sizeof(buffer), "%.1f%%", cached_utilisation_diff); RichString_appendnAscii(out, CRT_colors[color], buffer, len); + RichString_appendAscii(out, CRT_colors[METER_TEXT], " busy"); - RichString_appendAscii(out, CRT_colors[METER_TEXT], " read: "); - RichString_appendAscii(out, CRT_colors[METER_VALUE_IOREAD], cached_read_diff_str); - RichString_appendAscii(out, CRT_colors[METER_VALUE_IOREAD], "iB/s"); + if (cached_num_disks > 1 && cached_num_disks < 1000) { + RichString_appendAscii(out, CRT_colors[METER_TEXT], " ("); + len = xSnprintf(buffer, sizeof(buffer), "%u", (unsigned int)cached_num_disks); + RichString_appendnAscii(out, CRT_colors[METER_VALUE], buffer, len); + RichString_appendAscii(out, CRT_colors[METER_TEXT], " disks)"); + } +} - RichString_appendAscii(out, CRT_colors[METER_TEXT], " write: "); - RichString_appendAscii(out, CRT_colors[METER_VALUE_IOWRITE], cached_write_diff_str); - RichString_appendAscii(out, CRT_colors[METER_VALUE_IOWRITE], "iB/s"); +static void DiskIOMeter_display(const Object* cast, RichString* out) { + DiskIORateMeter_display(cast, out); + + switch (status) { + case RATESTATUS_NODATA: + case RATESTATUS_INIT: + case RATESTATUS_STALE: + return; + case RATESTATUS_DATA: + break; + } + + RichString_appendAscii(out, CRT_colors[METER_TEXT], "; "); + DiskIOTimeMeter_display(cast, out); } -const MeterClass DiskIOMeter_class = { +static void DiskIOMeter_updateValues(Meter* this) { + DiskIOMeterData* data = this->meterData; + + Meter_updateValues(data->diskIORateMeter); + Meter_updateValues(data->diskIOTimeMeter); +} + +static void DiskIOMeter_draw(Meter* this, int x, int y, int w) { + DiskIOMeterData* data = this->meterData; + + assert(data->diskIORateMeter->draw); + assert(data->diskIOTimeMeter->draw); + + switch (this->mode) { + case TEXT_METERMODE: + case LED_METERMODE: + data->diskIORateMeter->draw(this, x, y, w); + return; + } + + /* Use the same width for each sub meter to align with CPU meter */ + const int colwidth = w / 2; + const int diff = w % 2; + + data->diskIORateMeter->draw(data->diskIORateMeter, x, y, colwidth); + data->diskIOTimeMeter->draw(data->diskIOTimeMeter, x + colwidth + diff, y, colwidth); +} + +static void DiskIOMeter_init(Meter* this) { + if (!this->meterData) { + this->meterData = xCalloc(1, sizeof(DiskIOMeterData)); + } + + DiskIOMeterData* data = this->meterData; + + if (!data->diskIORateMeter) + data->diskIORateMeter = Meter_new(this->host, 0, (const MeterClass*) Class(DiskIORateMeter)); + if (!data->diskIOTimeMeter) + data->diskIOTimeMeter = Meter_new(this->host, 0, (const MeterClass*) Class(DiskIOTimeMeter)); + + if (Meter_initFn(data->diskIORateMeter)) { + Meter_init(data->diskIORateMeter); + } + if (Meter_initFn(data->diskIOTimeMeter)) { + Meter_init(data->diskIOTimeMeter); + } +} + +static void DiskIOMeter_updateMode(Meter* this, MeterModeId mode) { + DiskIOMeterData* data = this->meterData; + + this->mode = mode; + + Meter_setMode(data->diskIORateMeter, mode); + Meter_setMode(data->diskIOTimeMeter, mode); + + this->h = MAXIMUM(data->diskIORateMeter->h, data->diskIOTimeMeter->h); +} + +static void DiskIOMeter_done(Meter* this) { + DiskIOMeterData* data = this->meterData; + + Meter_delete((Object*)data->diskIORateMeter); + Meter_delete((Object*)data->diskIOTimeMeter); + + free(data); +} + +const MeterClass DiskIORateMeter_class = { .super = { .extends = Class(Meter), .delete = Meter_delete, - .display = DiskIOMeter_display + .display = DiskIORateMeter_display }, - .updateValues = DiskIOMeter_updateValues, + .updateValues = DiskIORateMeter_updateValues, + .defaultMode = TEXT_METERMODE, + .supportedModes = METERMODE_DEFAULT_SUPPORTED, + .maxItems = 2, + .isPercentChart = false, + .total = 1.0, + .attributes = DiskIORateMeter_attributes, + .name = "DiskIORate", + .uiName = "Disk IO Rate", + .description = "Disk IO read & write bytes per second", + .caption = "Dsk: " +}; + +const MeterClass DiskIOTimeMeter_class = { + .super = { + .extends = Class(Meter), + .delete = Meter_delete, + .display = DiskIOTimeMeter_display + }, + .updateValues = DiskIOTimeMeter_updateValues, .defaultMode = TEXT_METERMODE, .supportedModes = METERMODE_DEFAULT_SUPPORTED, .maxItems = 1, .isPercentChart = true, .total = 1.0, - .attributes = DiskIOMeter_attributes, + .attributes = DiskIOTimeMeter_attributes, + .name = "DiskIOTime", + .uiName = "Disk IO Time", + .description = "Disk percent time busy", + .caption = "Dsk: " +}; + +const MeterClass DiskIOMeter_class = { + .super = { + .extends = Class(Meter), + .delete = Meter_delete, + .display = DiskIOMeter_display + }, + .updateValues = DiskIOMeter_updateValues, + .defaultMode = TEXT_METERMODE, + .supportedModes = METERMODE_DEFAULT_SUPPORTED, + .isMultiColumn = true, .name = "DiskIO", .uiName = "Disk IO", - .caption = "Disk IO: " + .description = "Disk IO rate & time combined display", + .caption = "Dsk: ", + .draw = DiskIOMeter_draw, + .init = DiskIOMeter_init, + .updateMode = DiskIOMeter_updateMode, + .done = DiskIOMeter_done }; diff --git a/DiskIOMeter.h b/DiskIOMeter.h index b49adaf3a..2e1d29c1c 100644 --- a/DiskIOMeter.h +++ b/DiskIOMeter.h @@ -19,6 +19,10 @@ typedef struct DiskIOData_ { uint64_t numDisks; } DiskIOData; +extern const MeterClass DiskIORateMeter_class; + +extern const MeterClass DiskIOTimeMeter_class; + extern const MeterClass DiskIOMeter_class; #endif /* HEADER_DiskIOMeter */ diff --git a/darwin/Platform.c b/darwin/Platform.c index aa7400c87..e2ad672af 100644 --- a/darwin/Platform.c +++ b/darwin/Platform.c @@ -143,6 +143,8 @@ const MeterClass* const Platform_meterTypes[] = { &RightCPUs8Meter_class, &ZfsArcMeter_class, &ZfsCompressedArcMeter_class, + &DiskIORateMeter_class, + &DiskIOTimeMeter_class, &DiskIOMeter_class, &NetworkIOMeter_class, &FileDescriptorMeter_class, diff --git a/dragonflybsd/Platform.c b/dragonflybsd/Platform.c index ddd211771..46c367b10 100644 --- a/dragonflybsd/Platform.c +++ b/dragonflybsd/Platform.c @@ -120,6 +120,8 @@ const MeterClass* const Platform_meterTypes[] = { &RightCPUs4Meter_class, &LeftCPUs8Meter_class, &RightCPUs8Meter_class, + &DiskIORateMeter_class, + &DiskIOTimeMeter_class, &DiskIOMeter_class, &NetworkIOMeter_class, &FileDescriptorMeter_class, diff --git a/freebsd/Platform.c b/freebsd/Platform.c index 0eda02b7a..fce57f2a9 100644 --- a/freebsd/Platform.c +++ b/freebsd/Platform.c @@ -130,6 +130,8 @@ const MeterClass* const Platform_meterTypes[] = { &BlankMeter_class, &ZfsArcMeter_class, &ZfsCompressedArcMeter_class, + &DiskIORateMeter_class, + &DiskIOTimeMeter_class, &DiskIOMeter_class, &FileDescriptorMeter_class, &NetworkIOMeter_class, diff --git a/linux/Platform.c b/linux/Platform.c index 352c9c80b..a0bfbfa93 100644 --- a/linux/Platform.c +++ b/linux/Platform.c @@ -247,6 +247,8 @@ const MeterClass* const Platform_meterTypes[] = { &ZfsArcMeter_class, &ZfsCompressedArcMeter_class, &ZramMeter_class, + &DiskIORateMeter_class, + &DiskIOTimeMeter_class, &DiskIOMeter_class, &NetworkIOMeter_class, &SELinuxMeter_class, diff --git a/netbsd/Platform.c b/netbsd/Platform.c index af2a8613d..12e427cb5 100644 --- a/netbsd/Platform.c +++ b/netbsd/Platform.c @@ -180,6 +180,8 @@ const MeterClass* const Platform_meterTypes[] = { &LeftCPUs8Meter_class, &RightCPUs8Meter_class, &BlankMeter_class, + &DiskIORateMeter_class, + &DiskIOTimeMeter_class, &DiskIOMeter_class, &NetworkIOMeter_class, &FileDescriptorMeter_class, diff --git a/pcp/Platform.c b/pcp/Platform.c index e779ea0bc..f02fbde74 100644 --- a/pcp/Platform.c +++ b/pcp/Platform.c @@ -114,6 +114,8 @@ const MeterClass* const Platform_meterTypes[] = { &ZfsArcMeter_class, &ZfsCompressedArcMeter_class, &ZramMeter_class, + &DiskIORateMeter_class, + &DiskIOTimeMeter_class, &DiskIOMeter_class, &NetworkIOMeter_class, &SysArchMeter_class,