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