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