/* $NetBSD: meter.c,v 1.2 2021/08/14 16:14:58 christos Exp $ */
/* meter.c - lutil_meter meters */
/* $OpenLDAP$ */
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
*
* Copyright (c) 2009 by Emily Backes, Symas Corp.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
/* ACKNOWLEDGEMENTS:
* This work was initially developed by Emily Backes for inclusion
* in OpenLDAP software.
*/
#include <sys/cdefs.h>
__RCSID("$NetBSD: meter.c,v 1.2 2021/08/14 16:14:58 christos Exp $");
#include "portable.h"
#include "lutil_meter.h"
#include <ac/assert.h>
#include <ac/string.h>
int
lutil_time_string (
char *dest,
int duration,
int max_terms)
{
static const int time_div[] = {31556952,
604800,
86400,
3600,
60,
1,
0};
const int * time_divp = time_div;
static const char * time_name_ch = "ywdhms";
const char * time_name_chp = time_name_ch;
int term_count = 0;
char *buf = dest;
int time_quot;
assert ( max_terms >= 2 ); /* room for "none" message */
if ( duration < 0 ) {
*dest = '\0';
return 1;
}
if ( duration == 0 ) {
strcpy( dest, "none" );
return 0;
}
while ( term_count < max_terms && duration > 0 ) {
if (duration > *time_divp) {
time_quot = duration / *time_divp;
duration %= *time_divp;
if (time_quot > 99) {
return 1;
} else {
*(buf++) = time_quot / 10 + '0';
*(buf++) = time_quot % 10 + '0';
*(buf++) = *time_name_chp;
++term_count;
}
}
if ( *(++time_divp) == 0) duration = 0;
++time_name_chp;
}
*buf = '\0';
return 0;
}
int
lutil_get_now (double *now)
{
#ifdef HAVE_GETTIMEOFDAY
struct timeval tv;
assert( now );
gettimeofday( &tv, NULL );
*now = ((double) tv.tv_sec) + (((double) tv.tv_usec) / 1000000.0);
return 0;
#else
time_t tm;
assert( now );
time( &tm );
*now = (double) tm;
return 0;
#endif
}
int
lutil_meter_open (
lutil_meter_t *meter,
const lutil_meter_display_t *display,
const lutil_meter_estimator_t *estimator,
size_t goal_value)
{
int rc;
assert( meter != NULL );
assert( display != NULL );
assert( estimator != NULL );
if (goal_value < 1) return -1;
memset( (void*) meter, 0, sizeof( lutil_meter_t ));
meter->display = display;
meter->estimator = estimator;
lutil_get_now( &meter->start_time );
meter->last_update = meter->start_time;
meter->goal_value = goal_value;
meter->last_position = 0;
rc = meter->display->display_open( &meter->display_data );
if( rc != 0 ) return rc;
rc = meter->estimator->estimator_open( &meter->estimator_data );
if( rc != 0 ) {
meter->display->display_close( &meter->display_data );
return rc;
}
return 0;
}
int
lutil_meter_update (
lutil_meter_t *meter,
size_t position,
int force)
{
static const double display_rate = 0.5;
double frac, cycle_length, speed, now;
time_t remaining_time, elapsed;
int rc;
assert( meter != NULL );
lutil_get_now( &now );
if ( !force && now - meter->last_update < display_rate ) return 0;
frac = ((double)position) / ((double) meter->goal_value);
elapsed = now - meter->start_time;
if (frac <= 0.0 || elapsed == 0) return 0;
if (frac >= 1.0) {
rc = meter->display->display_update(
&meter->display_data,
1.0,
0,
(time_t) elapsed,
((double)position) / elapsed);
} else {
rc = meter->estimator->estimator_update(
&meter->estimator_data,
meter->start_time,
frac,
&remaining_time );
if ( rc == 0 ) {
cycle_length = now - meter->last_update;
speed = cycle_length > 0.0 ?
((double)(position - meter->last_position))
/ cycle_length :
0.0;
rc = meter->display->display_update(
&meter->display_data,
frac,
remaining_time,
(time_t) elapsed,
speed);
if ( rc == 0 ) {
meter->last_update = now;
meter->last_position = position;
}
}
}
return rc;
}
int
lutil_meter_close (lutil_meter_t *meter)
{
meter->estimator->estimator_close( &meter->estimator_data );
meter->display->display_close( &meter->display_data );
return 0;
}
/* Default display and estimator */
typedef struct {
int buffer_length;
char * buffer;
int need_eol;
int phase;
FILE *output;
} text_display_state_t;
static int
text_open (void ** display_datap)
{
static const int default_buffer_length = 81;
text_display_state_t *data;
assert( display_datap != NULL );
data = calloc( 1, sizeof( text_display_state_t ));
assert( data != NULL );
data->buffer_length = default_buffer_length;
data->buffer = calloc( 1, default_buffer_length );
assert( data->buffer != NULL );
data->output = stderr;
*display_datap = data;
return 0;
}
static int
text_update (
void **display_datap,
double frac,
time_t remaining_time,
time_t elapsed,
double byte_rate)
{
text_display_state_t *data;
char *buf, *buf_end;
assert( display_datap != NULL );
assert( *display_datap != NULL );
data = (text_display_state_t*) *display_datap;
if ( data->output == NULL ) return 1;
buf = data->buffer;
buf_end = buf + data->buffer_length - 1;
/* |#################### 100.00% eta 1d19h elapsed 23w 7d23h15m12s spd nnnn.n M/s */
{
/* spinner */
static const int phase_mod = 8;
static const char phase_char[] = "_.-*\"*-.";
*buf++ = phase_char[data->phase % phase_mod];
data->phase++;
}
{
/* bar */
static const int bar_length = 20;
static const double bar_lengthd = 20.0;
static const char fill_char = '#';
static const char blank_char = ' ';
char *bar_end = buf + bar_length;
char *bar_pos = frac < 0.0 ?
buf :
frac < 1.0 ?
buf + (int) (bar_lengthd * frac) :
bar_end;
assert( (buf_end - buf) > bar_length );
while ( buf < bar_end ) {
*buf = buf < bar_pos ?
fill_char : blank_char;
++buf;
}
}
{
/* percent */
(void) snprintf( buf, buf_end-buf, "%7.2f%%", 100.0*frac );
buf += 8;
}
{
/* eta and elapsed */
char time_buffer[19];
int rc;
rc = lutil_time_string( time_buffer, remaining_time, 2);
if (rc == 0)
snprintf( buf, buf_end-buf, " eta %6s", time_buffer );
buf += 5+6;
rc = lutil_time_string( time_buffer, elapsed, 5);
if (rc == 0)
snprintf( buf, buf_end-buf, " elapsed %15s",
time_buffer );
buf += 9+15;
}
{
/* speed */
static const char prefixes[] = " kMGTPEZY";
const char *prefix_chp = prefixes;
while (*prefix_chp && byte_rate >= 1024.0) {
byte_rate /= 1024.0;
++prefix_chp;
}
if ( byte_rate >= 1024.0 ) {
snprintf( buf, buf_end-buf, " fast!" );
buf += 6;
} else {
snprintf( buf, buf_end-buf, " spd %5.1f %c/s",
byte_rate,
*prefix_chp);
buf += 5+6+4;
}
}
(void) fprintf( data->output,
"\r%-79s",
data->buffer );
data->need_eol = 1;
return 0;
}
static int
text_close (void ** display_datap)
{
text_display_state_t *data;
if (display_datap) {
if (*display_datap) {
data = (text_display_state_t*) *display_datap;
if (data->output && data->need_eol)
fputs ("\n", data->output);
if (data->buffer)
free( data->buffer );
free( data );
}
*display_datap = NULL;
}
return 0;
}
static int
null_open_close (void **datap)
{
assert( datap );
*datap = NULL;
return 0;
}
static int
linear_update (
void **estimator_datap,
double start,
double frac,
time_t *remaining)
{
double now;
double elapsed;
assert( estimator_datap != NULL );
assert( *estimator_datap == NULL );
assert( start > 0.0 );
assert( frac >= 0.0 );
assert( frac <= 1.0 );
assert( remaining != NULL );
lutil_get_now( &now );
elapsed = now-start;
assert( elapsed >= 0.0 );
if ( frac == 0.0 ) {
return 1;
} else if ( frac >= 1.0 ) {
*remaining = 0;
return 0;
} else {
*remaining = (time_t) (elapsed/frac-elapsed+0.5);
return 0;
}
}
const lutil_meter_display_t lutil_meter_text_display = {
text_open, text_update, text_close
};
const lutil_meter_estimator_t lutil_meter_linear_estimator = {
null_open_close, linear_update, null_open_close
};