Home | History | Annotate | Line # | Download | only in liblutil
      1 /*	$NetBSD: meter.c,v 1.2 2021/08/14 16:14:58 christos Exp $	*/
      2 
      3 /* meter.c - lutil_meter meters */
      4 /* $OpenLDAP$ */
      5 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
      6  *
      7  * Copyright (c) 2009 by Emily Backes, Symas Corp.
      8  * All rights reserved.
      9  *
     10  * Redistribution and use in source and binary forms, with or without
     11  * modification, are permitted only as authorized by the OpenLDAP
     12  * Public License.
     13  *
     14  * A copy of this license is available in the file LICENSE in the
     15  * top-level directory of the distribution or, alternatively, at
     16  * <http://www.OpenLDAP.org/license.html>.
     17  */
     18 /* ACKNOWLEDGEMENTS:
     19  * This work was initially developed by Emily Backes for inclusion
     20  * in OpenLDAP software.
     21  */
     22 
     23 #include <sys/cdefs.h>
     24 __RCSID("$NetBSD: meter.c,v 1.2 2021/08/14 16:14:58 christos Exp $");
     25 
     26 #include "portable.h"
     27 #include "lutil_meter.h"
     28 
     29 #include <ac/assert.h>
     30 #include <ac/string.h>
     31 
     32 int
     33 lutil_time_string (
     34 	char *dest,
     35 	int duration,
     36 	int max_terms)
     37 {
     38 	static const int time_div[] = {31556952,
     39 				       604800,
     40 				       86400,
     41 				       3600,
     42 				       60,
     43 				       1,
     44 				       0};
     45 	const int * time_divp = time_div;
     46 	static const char * time_name_ch = "ywdhms";
     47 	const char * time_name_chp = time_name_ch;
     48 	int term_count = 0;
     49 	char *buf = dest;
     50 	int time_quot;
     51 
     52 	assert ( max_terms >= 2 ); /* room for "none" message */
     53 
     54 	if ( duration < 0 ) {
     55 		*dest = '\0';
     56 		return 1;
     57 	}
     58 	if ( duration == 0 ) {
     59 		strcpy( dest, "none" );
     60 		return 0;
     61 	}
     62 	while ( term_count < max_terms && duration > 0 ) {
     63 		if (duration > *time_divp) {
     64 			time_quot = duration / *time_divp;
     65 			duration %= *time_divp;
     66 			if (time_quot > 99) {
     67 				return 1;
     68 			} else {
     69 				*(buf++) = time_quot / 10 + '0';
     70 				*(buf++) = time_quot % 10 + '0';
     71 				*(buf++) = *time_name_chp;
     72 				++term_count;
     73 			}
     74 		}
     75 		if ( *(++time_divp) == 0) duration = 0;
     76 		++time_name_chp;
     77 	}
     78 	*buf = '\0';
     79 	return 0;
     80 }
     81 
     82 int
     83 lutil_get_now (double *now)
     84 {
     85 #ifdef HAVE_GETTIMEOFDAY
     86 	struct timeval tv;
     87 
     88 	assert( now );
     89 	gettimeofday( &tv, NULL );
     90 	*now = ((double) tv.tv_sec) + (((double) tv.tv_usec) / 1000000.0);
     91 	return 0;
     92 #else
     93 	time_t tm;
     94 
     95 	assert( now );
     96 	time( &tm );
     97 	*now = (double) tm;
     98 	return 0;
     99 #endif
    100 }
    101 
    102 int
    103 lutil_meter_open (
    104 	lutil_meter_t *meter,
    105 	const lutil_meter_display_t *display,
    106 	const lutil_meter_estimator_t *estimator,
    107 	size_t goal_value)
    108 {
    109 	int rc;
    110 
    111 	assert( meter != NULL );
    112 	assert( display != NULL );
    113 	assert( estimator != NULL );
    114 
    115 	if (goal_value < 1) return -1;
    116 
    117 	memset( (void*) meter, 0, sizeof( lutil_meter_t ));
    118 	meter->display = display;
    119 	meter->estimator = estimator;
    120 	lutil_get_now( &meter->start_time );
    121 	meter->last_update = meter->start_time;
    122 	meter->goal_value = goal_value;
    123 	meter->last_position = 0;
    124 
    125 	rc = meter->display->display_open( &meter->display_data );
    126 	if( rc != 0 ) return rc;
    127 
    128 	rc = meter->estimator->estimator_open( &meter->estimator_data );
    129 	if( rc != 0 ) {
    130 		meter->display->display_close( &meter->display_data );
    131 		return rc;
    132 	}
    133 
    134 	return 0;
    135 }
    136 
    137 int
    138 lutil_meter_update (
    139 	lutil_meter_t *meter,
    140 	size_t position,
    141 	int force)
    142 {
    143 	static const double display_rate = 0.5;
    144 	double frac, cycle_length, speed, now;
    145 	time_t remaining_time, elapsed;
    146 	int rc;
    147 
    148 	assert( meter != NULL );
    149 
    150 	lutil_get_now( &now );
    151 
    152 	if ( !force && now - meter->last_update < display_rate ) return 0;
    153 
    154 	frac = ((double)position) / ((double) meter->goal_value);
    155 	elapsed = now - meter->start_time;
    156 	if (frac <= 0.0 || elapsed == 0) return 0;
    157 	if (frac >= 1.0) {
    158 		rc = meter->display->display_update(
    159 			&meter->display_data,
    160 			1.0,
    161 			0,
    162 			(time_t) elapsed,
    163 			((double)position) / elapsed);
    164 	} else {
    165 		rc = meter->estimator->estimator_update(
    166 			&meter->estimator_data,
    167 			meter->start_time,
    168 			frac,
    169 			&remaining_time );
    170 		if ( rc == 0 ) {
    171 			cycle_length = now - meter->last_update;
    172 			speed = cycle_length > 0.0 ?
    173 				((double)(position - meter->last_position))
    174 				/ cycle_length :
    175 				0.0;
    176 			rc = meter->display->display_update(
    177 				&meter->display_data,
    178 				frac,
    179 				remaining_time,
    180 				(time_t) elapsed,
    181 				speed);
    182 			if ( rc == 0 ) {
    183 				meter->last_update = now;
    184 				meter->last_position = position;
    185 			}
    186 		}
    187 	}
    188 
    189 	return rc;
    190 }
    191 
    192 int
    193 lutil_meter_close (lutil_meter_t *meter)
    194 {
    195 	meter->estimator->estimator_close( &meter->estimator_data );
    196 	meter->display->display_close( &meter->display_data );
    197 
    198 	return 0;
    199 }
    200 
    201 /* Default display and estimator */
    202 typedef struct {
    203 	int buffer_length;
    204 	char * buffer;
    205 	int need_eol;
    206 	int phase;
    207 	FILE *output;
    208 } text_display_state_t;
    209 
    210 static int
    211 text_open (void ** display_datap)
    212 {
    213 	static const int default_buffer_length = 81;
    214 	text_display_state_t *data;
    215 
    216 	assert( display_datap != NULL );
    217 	data = calloc( 1, sizeof( text_display_state_t ));
    218 	assert( data != NULL );
    219 	data->buffer_length = default_buffer_length;
    220 	data->buffer = calloc( 1, default_buffer_length );
    221 	assert( data->buffer != NULL );
    222 	data->output = stderr;
    223 	*display_datap = data;
    224 	return 0;
    225 }
    226 
    227 static int
    228 text_update (
    229 	void **display_datap,
    230 	double frac,
    231 	time_t remaining_time,
    232 	time_t elapsed,
    233 	double byte_rate)
    234 {
    235 	text_display_state_t *data;
    236 	char *buf, *buf_end;
    237 
    238 	assert( display_datap != NULL );
    239 	assert( *display_datap != NULL );
    240 	data = (text_display_state_t*) *display_datap;
    241 
    242 	if ( data->output == NULL ) return 1;
    243 
    244 	buf = data->buffer;
    245 	buf_end = buf + data->buffer_length - 1;
    246 
    247 /* |#################### 100.00% eta  1d19h elapsed 23w 7d23h15m12s spd nnnn.n M/s */
    248 
    249 	{
    250 		/* spinner */
    251 		static const int phase_mod = 8;
    252 		static const char phase_char[] = "_.-*\"*-.";
    253 		*buf++ = phase_char[data->phase % phase_mod];
    254 		data->phase++;
    255 	}
    256 
    257 	{
    258 		/* bar */
    259 		static const int bar_length = 20;
    260 		static const double bar_lengthd = 20.0;
    261 		static const char fill_char = '#';
    262 		static const char blank_char = ' ';
    263 		char *bar_end = buf + bar_length;
    264 		char *bar_pos = frac < 0.0 ?
    265 			buf :
    266 			frac < 1.0 ?
    267 			buf + (int) (bar_lengthd * frac) :
    268 			bar_end;
    269 
    270 		assert( (buf_end - buf) > bar_length );
    271 		while ( buf < bar_end ) {
    272 			*buf = buf < bar_pos ?
    273 				fill_char : blank_char;
    274 			++buf;
    275 		}
    276 	}
    277 
    278 	{
    279 		/* percent */
    280 		(void) snprintf( buf, buf_end-buf, "%7.2f%%", 100.0*frac );
    281 		buf += 8;
    282 	}
    283 
    284 	{
    285 		/* eta and elapsed */
    286 		char time_buffer[19];
    287 		int rc;
    288 		rc = lutil_time_string( time_buffer, remaining_time, 2);
    289 		if (rc == 0)
    290 			snprintf( buf, buf_end-buf, " eta %6s", time_buffer );
    291 		buf += 5+6;
    292 		rc = lutil_time_string( time_buffer, elapsed, 5);
    293 		if (rc == 0)
    294 			snprintf( buf, buf_end-buf, " elapsed %15s",
    295 				  time_buffer );
    296 		buf += 9+15;
    297 	}
    298 
    299 	{
    300 		/* speed */
    301 		static const char prefixes[] = " kMGTPEZY";
    302 		const char *prefix_chp = prefixes;
    303 
    304 		while (*prefix_chp && byte_rate >= 1024.0) {
    305 			byte_rate /= 1024.0;
    306 			++prefix_chp;
    307 		}
    308 		if ( byte_rate >= 1024.0 ) {
    309 			snprintf( buf, buf_end-buf, " fast!" );
    310 			buf += 6;
    311 		} else {
    312 			snprintf( buf, buf_end-buf, " spd %5.1f %c/s",
    313 				  byte_rate,
    314 				  *prefix_chp);
    315 			buf += 5+6+4;
    316 		}
    317 	}
    318 
    319 	(void) fprintf( data->output,
    320 			"\r%-79s",
    321 			data->buffer );
    322 	data->need_eol = 1;
    323 	return 0;
    324 }
    325 
    326 static int
    327 text_close (void ** display_datap)
    328 {
    329 	text_display_state_t *data;
    330 
    331 	if (display_datap) {
    332 		if (*display_datap) {
    333 			data = (text_display_state_t*) *display_datap;
    334 			if (data->output && data->need_eol)
    335 				fputs ("\n", data->output);
    336 			if (data->buffer)
    337 				free( data->buffer );
    338 			free( data );
    339 		}
    340 		*display_datap = NULL;
    341 	}
    342 	return 0;
    343 }
    344 
    345 static int
    346 null_open_close (void **datap)
    347 {
    348 	assert( datap );
    349 	*datap = NULL;
    350 	return 0;
    351 }
    352 
    353 static int
    354 linear_update (
    355 	void **estimator_datap,
    356 	double start,
    357 	double frac,
    358 	time_t *remaining)
    359 {
    360 	double now;
    361 	double elapsed;
    362 
    363 	assert( estimator_datap != NULL );
    364 	assert( *estimator_datap == NULL );
    365 	assert( start > 0.0 );
    366 	assert( frac >= 0.0 );
    367 	assert( frac <= 1.0 );
    368 	assert( remaining != NULL );
    369 	lutil_get_now( &now );
    370 
    371 	elapsed = now-start;
    372 	assert( elapsed >= 0.0 );
    373 
    374 	if ( frac == 0.0 ) {
    375 		return 1;
    376 	} else if ( frac >= 1.0 ) {
    377 		*remaining = 0;
    378 		return 0;
    379 	} else {
    380 		*remaining = (time_t) (elapsed/frac-elapsed+0.5);
    381 		return 0;
    382 	}
    383 }
    384 
    385 const lutil_meter_display_t lutil_meter_text_display = {
    386 	text_open, text_update, text_close
    387 };
    388 
    389 const lutil_meter_estimator_t lutil_meter_linear_estimator = {
    390 	null_open_close, linear_update, null_open_close
    391 };
    392