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
lutil_time_string(char * dest,int duration,int max_terms)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
lutil_get_now(double * now)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
lutil_meter_open(lutil_meter_t * meter,const lutil_meter_display_t * display,const lutil_meter_estimator_t * estimator,size_t goal_value)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
lutil_meter_update(lutil_meter_t * meter,size_t position,int force)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
lutil_meter_close(lutil_meter_t * meter)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
text_open(void ** display_datap)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
text_update(void ** display_datap,double frac,time_t remaining_time,time_t elapsed,double byte_rate)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
text_close(void ** display_datap)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
null_open_close(void ** datap)346 null_open_close (void **datap)
347 {
348           assert( datap );
349           *datap = NULL;
350           return 0;
351 }
352 
353 static int
linear_update(void ** estimator_datap,double start,double frac,time_t * remaining)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