]> git.sesse.net Git - vlc/blob - src/misc/stats.c
048b61ba45ac171a220abb1284162c6a82e3eddf
[vlc] / src / misc / stats.c
1 /*****************************************************************************
2  * stats.c: Statistics handling
3  *****************************************************************************
4  * Copyright (C) 2006 the VideoLAN team
5  * $Id$
6  *
7  * Authors: ClĂ©ment Stenac <zorglub@videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31
32 #include <vlc_common.h>
33 #include <stdio.h>                                               /* required */
34
35 #include "input/input_internal.h"
36
37 /*****************************************************************************
38  * Local prototypes
39  *****************************************************************************/
40 static int CounterUpdate( vlc_object_t *p_this,
41                           counter_t *p_counter,
42                           vlc_value_t val, vlc_value_t * );
43 static void TimerDump( vlc_object_t *p_this, counter_t *p_counter, bool);
44
45 /*****************************************************************************
46  * Exported functions
47  *****************************************************************************/
48
49 /**
50  * Create a statistics counter
51  * \param p_this a VLC object
52  * \param i_type the type of stored data. One of VLC_VAR_STRING,
53  * VLC_VAR_INTEGER, VLC_VAR_FLOAT
54  * \param i_compute_type the aggregation type. One of STATS_LAST (always
55  * keep the last value), STATS_COUNTER (increment by the passed value),
56  * STATS_MAX (keep the maximum passed value), STATS_MIN, or STATS_DERIVATIVE
57  * (keep a time derivative of the value)
58  */
59 counter_t * __stats_CounterCreate( vlc_object_t *p_this,
60                                    int i_type, int i_compute_type )
61 {
62     counter_t *p_counter = (counter_t*) malloc( sizeof( counter_t ) ) ;
63     (void)p_this;
64
65     if( !p_counter ) return NULL;
66     p_counter->i_compute_type = i_compute_type;
67     p_counter->i_type = i_type;
68     p_counter->i_samples = 0;
69     p_counter->pp_samples = NULL;
70     p_counter->psz_name = NULL;
71
72     p_counter->update_interval = 0;
73     p_counter->last_update = 0;
74
75     return p_counter;
76 }
77
78 /** Update a counter element with new values
79  * \param p_this a VLC object
80  * \param p_counter the counter to update
81  * \param val the vlc_value union containing the new value to aggregate. For
82  * more information on how data is aggregated, \see __stats_Create
83  * \param val_new a pointer that will be filled with new data
84  */
85 int __stats_Update( vlc_object_t *p_this, counter_t *p_counter,
86                     vlc_value_t val, vlc_value_t *val_new )
87 {
88     if( !libvlc_stats (p_this) || !p_counter ) return VLC_EGENERIC;
89     return CounterUpdate( p_this, p_counter, val, val_new );
90 }
91
92 /** Get the aggregated value for a counter
93  * \param p_this an object
94  * \param p_counter the counter
95  * \param val a pointer to an initialized vlc_value union. It will contain the
96  * retrieved value
97  * \return an error code
98  */
99 int __stats_Get( vlc_object_t *p_this, counter_t *p_counter, vlc_value_t *val )
100 {
101     if( !libvlc_stats (p_this) || !p_counter || p_counter->i_samples == 0 )
102     {
103         val->i_int = val->f_float = 0.0;
104         return VLC_EGENERIC;
105     }
106
107     switch( p_counter->i_compute_type )
108     {
109     case STATS_LAST:
110     case STATS_MIN:
111     case STATS_MAX:
112     case STATS_COUNTER:
113         *val = p_counter->pp_samples[0]->value;
114         break;
115     case STATS_DERIVATIVE:
116         /* Not ready yet */
117         if( p_counter->i_samples < 2 )
118         {
119             val->i_int = 0; val->f_float = 0.0;
120             return VLC_EGENERIC;
121         }
122         if( p_counter->i_type == VLC_VAR_INTEGER )
123         {
124             float f = ( p_counter->pp_samples[0]->value.i_int -
125                         p_counter->pp_samples[1]->value.i_int ) /
126                     (float)(  p_counter->pp_samples[0]->date -
127                               p_counter->pp_samples[1]->date );
128             val->i_int = (int)f;
129         }
130         else
131         {
132             float f = (float)( p_counter->pp_samples[0]->value.f_float -
133                                p_counter->pp_samples[1]->value.f_float ) /
134                       (float)( p_counter->pp_samples[0]->date -
135                                p_counter->pp_samples[1]->date );
136             val->f_float = f;
137         }
138         break;
139     }
140     return VLC_SUCCESS;;
141 }
142
143 input_stats_t *stats_NewInputStats( input_thread_t *p_input )
144 {
145     (void)p_input;
146     input_stats_t *p_stats = calloc( 1, sizeof(input_stats_t) );
147     if( !p_stats )
148         return NULL;
149
150     vlc_mutex_init( &p_stats->lock );
151     stats_ReinitInputStats( p_stats );
152
153     return p_stats;
154 }
155
156 void stats_ComputeInputStats( input_thread_t *p_input, input_stats_t *p_stats )
157 {
158     if( !libvlc_stats (p_input) ) return;
159
160     vlc_mutex_lock( &p_input->p->counters.counters_lock );
161     vlc_mutex_lock( &p_stats->lock );
162
163     /* Input */
164     stats_GetInteger( p_input, p_input->p->counters.p_read_packets,
165                       &p_stats->i_read_packets );
166     stats_GetInteger( p_input, p_input->p->counters.p_read_bytes,
167                       &p_stats->i_read_bytes );
168     stats_GetFloat( p_input, p_input->p->counters.p_input_bitrate,
169                     &p_stats->f_input_bitrate );
170     stats_GetInteger( p_input, p_input->p->counters.p_demux_read,
171                       &p_stats->i_demux_read_bytes );
172     stats_GetFloat( p_input, p_input->p->counters.p_demux_bitrate,
173                     &p_stats->f_demux_bitrate );
174     stats_GetInteger( p_input, p_input->p->counters.p_demux_corrupted,
175                       &p_stats->i_demux_corrupted );
176     stats_GetInteger( p_input, p_input->p->counters.p_demux_discontinuity,
177                       &p_stats->i_demux_discontinuity );
178
179     /* Decoders */
180     stats_GetInteger( p_input, p_input->p->counters.p_decoded_video,
181                       &p_stats->i_decoded_video );
182     stats_GetInteger( p_input, p_input->p->counters.p_decoded_audio,
183                       &p_stats->i_decoded_audio );
184
185     /* Sout */
186     if( p_input->p->counters.p_sout_send_bitrate )
187     {
188         stats_GetInteger( p_input, p_input->p->counters.p_sout_sent_packets,
189                           &p_stats->i_sent_packets );
190         stats_GetInteger( p_input, p_input->p->counters.p_sout_sent_bytes,
191                           &p_stats->i_sent_bytes );
192         stats_GetFloat  ( p_input, p_input->p->counters.p_sout_send_bitrate,
193                           &p_stats->f_send_bitrate );
194     }
195
196     /* Aout */
197     stats_GetInteger( p_input, p_input->p->counters.p_played_abuffers,
198                       &p_stats->i_played_abuffers );
199     stats_GetInteger( p_input, p_input->p->counters.p_lost_abuffers,
200                       &p_stats->i_lost_abuffers );
201
202     /* Vouts */
203     stats_GetInteger( p_input, p_input->p->counters.p_displayed_pictures,
204                       &p_stats->i_displayed_pictures );
205     stats_GetInteger( p_input, p_input->p->counters.p_lost_pictures,
206                       &p_stats->i_lost_pictures );
207
208     vlc_mutex_unlock( &p_stats->lock );
209     vlc_mutex_unlock( &p_input->p->counters.counters_lock );
210 }
211
212 void stats_ReinitInputStats( input_stats_t *p_stats )
213 {
214     vlc_mutex_lock( &p_stats->lock );
215     p_stats->i_read_packets = p_stats->i_read_bytes =
216     p_stats->f_input_bitrate = p_stats->f_average_input_bitrate =
217     p_stats->i_demux_read_packets = p_stats->i_demux_read_bytes =
218     p_stats->f_demux_bitrate = p_stats->f_average_demux_bitrate =
219     p_stats->i_demux_corrupted = p_stats->i_demux_discontinuity =
220     p_stats->i_displayed_pictures = p_stats->i_lost_pictures =
221     p_stats->i_played_abuffers = p_stats->i_lost_abuffers =
222     p_stats->i_decoded_video = p_stats->i_decoded_audio =
223     p_stats->i_sent_bytes = p_stats->i_sent_packets = p_stats->f_send_bitrate
224      = 0;
225     vlc_mutex_unlock( &p_stats->lock );
226 }
227
228 void stats_DumpInputStats( input_stats_t *p_stats  )
229 {
230     vlc_mutex_lock( &p_stats->lock );
231     /* f_bitrate is in bytes / microsecond
232      * *1000 => bytes / millisecond => kbytes / seconds */
233     fprintf( stderr, "Input : %i (%i bytes) - %f kB/s - "
234                      "Demux : %i (%i bytes) - %f kB/s\n"
235                      " - Vout : %i/%i - Aout : %i/%i - Sout : %f\n",
236                     p_stats->i_read_packets, p_stats->i_read_bytes,
237                     p_stats->f_input_bitrate * 1000,
238                     p_stats->i_demux_read_packets, p_stats->i_demux_read_bytes,
239                     p_stats->f_demux_bitrate * 1000,
240                     p_stats->i_displayed_pictures, p_stats->i_lost_pictures,
241                     p_stats->i_played_abuffers, p_stats->i_lost_abuffers,
242                     p_stats->f_send_bitrate );
243     vlc_mutex_unlock( &p_stats->lock );
244 }
245
246 void __stats_TimerStart( vlc_object_t *p_obj, const char *psz_name,
247                          unsigned int i_id )
248 {
249     libvlc_priv_t *priv = libvlc_priv (p_obj->p_libvlc);
250     counter_t *p_counter = NULL;
251
252     if( !priv->b_stats ) return;
253
254     vlc_mutex_lock( &priv->timer_lock );
255
256     for( int i = 0 ; i < priv->i_timers; i++ )
257     {
258         if( priv->pp_timers[i]->i_id == i_id
259             && priv->pp_timers[i]->p_obj == p_obj )
260         {
261             p_counter = priv->pp_timers[i];
262             break;
263         }
264     }
265     if( !p_counter )
266     {
267         counter_sample_t *p_sample;
268         p_counter = stats_CounterCreate( p_obj->p_libvlc, VLC_VAR_TIME,
269                                          STATS_TIMER );
270         if( !p_counter )
271             goto out;
272         p_counter->psz_name = strdup( psz_name );
273         p_counter->i_id = i_id;
274         p_counter->p_obj = p_obj;
275         INSERT_ELEM( priv->pp_timers, priv->i_timers,
276                      priv->i_timers, p_counter );
277
278         /* 1st sample : if started: start_date, else last_time, b_started */
279         p_sample = (counter_sample_t *)malloc( sizeof( counter_sample_t ) );
280         INSERT_ELEM( p_counter->pp_samples, p_counter->i_samples,
281                      p_counter->i_samples, p_sample );
282         p_sample->date = 0; p_sample->value.b_bool = 0;
283         /* 2nd sample : global_time, i_samples */
284         p_sample = (counter_sample_t *)malloc( sizeof( counter_sample_t ) );
285         INSERT_ELEM( p_counter->pp_samples, p_counter->i_samples,
286                      p_counter->i_samples, p_sample );
287         p_sample->date = 0; p_sample->value.i_int = 0;
288     }
289     if( p_counter->pp_samples[0]->value.b_bool == true )
290     {
291         msg_Warn( p_obj, "timer '%s' was already started !", psz_name );
292         goto out;
293     }
294     p_counter->pp_samples[0]->value.b_bool = true;
295     p_counter->pp_samples[0]->date = mdate();
296 out:
297     vlc_mutex_unlock( &priv->timer_lock );
298 }
299
300 void __stats_TimerStop( vlc_object_t *p_obj, unsigned int i_id )
301 {
302     counter_t *p_counter = NULL;
303     libvlc_priv_t *priv = libvlc_priv (p_obj->p_libvlc);
304
305     if( !priv->b_stats ) return;
306     vlc_mutex_lock( &priv->timer_lock );
307     for( int i = 0 ; i < priv->i_timers; i++ )
308     {
309         if( priv->pp_timers[i]->i_id == i_id
310             && priv->pp_timers[i]->p_obj == p_obj )
311         {
312             p_counter = priv->pp_timers[i];
313             break;
314         }
315     }
316     if( !p_counter || p_counter->i_samples != 2 )
317     {
318         msg_Err( p_obj, "timer does not exist" );
319         goto out;
320     }
321     p_counter->pp_samples[0]->value.b_bool = false;
322     p_counter->pp_samples[1]->value.i_int += 1;
323     p_counter->pp_samples[0]->date = mdate() - p_counter->pp_samples[0]->date;
324     p_counter->pp_samples[1]->date += p_counter->pp_samples[0]->date;
325 out:
326     vlc_mutex_unlock( &priv->timer_lock );
327 }
328
329 void __stats_TimerDump( vlc_object_t *p_obj, unsigned int i_id )
330 {
331     counter_t *p_counter = NULL;
332     libvlc_priv_t *priv = libvlc_priv (p_obj->p_libvlc);
333
334     if( !priv->b_stats ) return;
335     vlc_mutex_lock( &priv->timer_lock );
336     for( int i = 0 ; i < priv->i_timers; i++ )
337     {
338         if( priv->pp_timers[i]->i_id == i_id
339             && priv->pp_timers[i]->p_obj == p_obj )
340         {
341             p_counter = priv->pp_timers[i];
342             break;
343         }
344     }
345     TimerDump( p_obj, p_counter, true );
346     vlc_mutex_unlock( &priv->timer_lock );
347 }
348
349 void __stats_TimersDumpAll( vlc_object_t *p_obj )
350 {
351     libvlc_priv_t *priv = libvlc_priv (p_obj->p_libvlc);
352
353     if( !priv->b_stats ) return;
354     vlc_mutex_lock( &priv->timer_lock );
355     for ( int i = 0 ; i < priv->i_timers ; i++ )
356         TimerDump( p_obj, priv->pp_timers[i], false );
357     vlc_mutex_unlock( &priv->timer_lock );
358 }
359
360 void __stats_TimerClean( vlc_object_t *p_obj, unsigned int i_id )
361 {
362     libvlc_priv_t *priv = libvlc_priv (p_obj->p_libvlc);
363
364     vlc_mutex_lock( &priv->timer_lock );
365     for ( int i = priv->i_timers -1 ; i >= 0; i-- )
366     {
367         counter_t *p_counter = priv->pp_timers[i];
368         if( p_counter->i_id == i_id && p_counter->p_obj == p_obj )
369         {
370             REMOVE_ELEM( priv->pp_timers, priv->i_timers, i );
371             stats_CounterClean( p_counter );
372         }
373     }
374     vlc_mutex_unlock( &priv->timer_lock );
375 }
376
377 void __stats_TimersCleanAll( vlc_object_t *p_obj )
378 {
379     libvlc_priv_t *priv = libvlc_priv (p_obj->p_libvlc);
380
381     vlc_mutex_lock( &priv->timer_lock );
382     for ( int i = priv->i_timers -1 ; i >= 0; i-- )
383     {
384         counter_t *p_counter = priv->pp_timers[i];
385         REMOVE_ELEM( priv->pp_timers, priv->i_timers, i );
386         stats_CounterClean( p_counter );
387     }
388     vlc_mutex_unlock( &priv->timer_lock );
389 }
390
391 void stats_CounterClean( counter_t *p_c )
392 {
393     if( p_c )
394     {
395         int i = p_c->i_samples - 1 ;
396         while( i >= 0 )
397         {
398             counter_sample_t *p_s = p_c->pp_samples[i];
399             REMOVE_ELEM( p_c->pp_samples, p_c->i_samples, i );
400             free( p_s );
401             i--;
402         }
403         free( p_c->psz_name );
404         free( p_c );
405     }
406 }
407
408
409 /********************************************************************
410  * Following functions are local
411  ********************************************************************/
412
413 /**
414  * Update a statistics counter, according to its type
415  * If needed, perform a bit of computation (derivative, mostly)
416  * This function must be entered with stats handler lock
417  * \param p_counter the counter to update
418  * \param val the "new" value
419  * \return an error code
420  */
421 static int CounterUpdate( vlc_object_t *p_handler,
422                           counter_t *p_counter,
423                           vlc_value_t val, vlc_value_t *new_val )
424 {
425     switch( p_counter->i_compute_type )
426     {
427     case STATS_LAST:
428     case STATS_MIN:
429     case STATS_MAX:
430         if( p_counter->i_samples > 1)
431         {
432             msg_Err( p_handler, "LAST counter has several samples !" );
433             return VLC_EGENERIC;
434         }
435         if( p_counter->i_type != VLC_VAR_FLOAT &&
436             p_counter->i_type != VLC_VAR_INTEGER &&
437             p_counter->i_compute_type != STATS_LAST )
438         {
439             msg_Err( p_handler, "unable to compute MIN or MAX for this type");
440             return VLC_EGENERIC;
441         }
442
443         if( p_counter->i_samples == 0 )
444         {
445             counter_sample_t *p_new = (counter_sample_t*)malloc(
446                                                sizeof( counter_sample_t ) );
447             p_new->value.psz_string = NULL;
448
449             INSERT_ELEM( p_counter->pp_samples, p_counter->i_samples,
450                          p_counter->i_samples, p_new );
451         }
452         if( p_counter->i_samples == 1 )
453         {
454             /* Update if : LAST or (MAX and bigger) or (MIN and bigger) */
455             if( p_counter->i_compute_type == STATS_LAST ||
456                 ( p_counter->i_compute_type == STATS_MAX &&
457                    ( ( p_counter->i_type == VLC_VAR_INTEGER &&
458                        p_counter->pp_samples[0]->value.i_int > val.i_int ) ||
459                      ( p_counter->i_type == VLC_VAR_FLOAT &&
460                        p_counter->pp_samples[0]->value.f_float > val.f_float )
461                    ) ) ||
462                 ( p_counter->i_compute_type == STATS_MIN &&
463                    ( ( p_counter->i_type == VLC_VAR_INTEGER &&
464                        p_counter->pp_samples[0]->value.i_int < val.i_int ) ||
465                      ( p_counter->i_type == VLC_VAR_FLOAT &&
466                        p_counter->pp_samples[0]->value.f_float < val.f_float )
467                    ) ) )
468             {
469                 if( p_counter->i_type == VLC_VAR_STRING &&
470                     p_counter->pp_samples[0]->value.psz_string )
471                 {
472                     free( p_counter->pp_samples[0]->value.psz_string );
473                 }
474                 p_counter->pp_samples[0]->value = val;
475                 *new_val = p_counter->pp_samples[0]->value;
476             }
477         }
478         break;
479     case STATS_DERIVATIVE:
480     {
481         counter_sample_t *p_new, *p_old;
482         mtime_t now = mdate();
483         if( now - p_counter->last_update < p_counter->update_interval )
484         {
485             return VLC_EGENERIC;
486         }
487         p_counter->last_update = now;
488         if( p_counter->i_type != VLC_VAR_FLOAT &&
489             p_counter->i_type != VLC_VAR_INTEGER )
490         {
491             msg_Err( p_handler, "Unable to compute DERIVATIVE for this type");
492             return VLC_EGENERIC;
493         }
494         /* Insert the new one at the beginning */
495         p_new = (counter_sample_t*)malloc( sizeof( counter_sample_t ) );
496         p_new->value = val;
497         p_new->date = p_counter->last_update;
498         INSERT_ELEM( p_counter->pp_samples, p_counter->i_samples,
499                      0, p_new );
500
501         if( p_counter->i_samples == 3 )
502         {
503             p_old = p_counter->pp_samples[2];
504             REMOVE_ELEM( p_counter->pp_samples, p_counter->i_samples, 2 );
505             free( p_old );
506         }
507         break;
508     }
509     case STATS_COUNTER:
510         if( p_counter->i_samples > 1)
511         {
512             msg_Err( p_handler, "LAST counter has several samples !" );
513             return VLC_EGENERIC;
514         }
515         if( p_counter->i_samples == 0 )
516         {
517             counter_sample_t *p_new = (counter_sample_t*)malloc(
518                                                sizeof( counter_sample_t ) );
519             p_new->value.psz_string = NULL;
520
521             INSERT_ELEM( p_counter->pp_samples, p_counter->i_samples,
522                          p_counter->i_samples, p_new );
523         }
524         if( p_counter->i_samples == 1 )
525         {
526             switch( p_counter->i_type )
527             {
528             case VLC_VAR_INTEGER:
529                 p_counter->pp_samples[0]->value.i_int += val.i_int;
530                 if( new_val )
531                     new_val->i_int = p_counter->pp_samples[0]->value.i_int;
532                 break;
533             case VLC_VAR_FLOAT:
534                 p_counter->pp_samples[0]->value.f_float += val.f_float;
535                 if( new_val )
536                     new_val->f_float = p_counter->pp_samples[0]->value.f_float;
537             default:
538                 msg_Err( p_handler, "Trying to increment invalid variable %s",
539                          p_counter->psz_name );
540                 return VLC_EGENERIC;
541             }
542         }
543         break;
544     }
545     return VLC_SUCCESS;
546 }
547
548 static void TimerDump( vlc_object_t *p_obj, counter_t *p_counter,
549                        bool b_total )
550 {
551     if( !p_counter )
552         return;
553
554     mtime_t last, total;
555     int i_total;
556     if( p_counter->i_samples != 2 )
557     {
558         msg_Err( p_obj, "timer %s does not exist", p_counter->psz_name );
559         return;
560     }
561     i_total = p_counter->pp_samples[1]->value.i_int;
562     total = p_counter->pp_samples[1]->date;
563     if( p_counter->pp_samples[0]->value.b_bool == true )
564     {
565         last = mdate() - p_counter->pp_samples[0]->date;
566         i_total += 1;
567         total += last;
568     }
569     else
570     {
571         last = p_counter->pp_samples[0]->date;
572     }
573     if( b_total )
574     {
575         msg_Dbg( p_obj,
576              "TIMER %s : %.3f ms - Total %.3f ms / %i intvls (Avg %.3f ms)",
577              p_counter->psz_name, (float)last/1000, (float)total/1000, i_total,
578              (float)(total)/(1000*(float)i_total ) );
579     }
580     else
581     {
582         msg_Dbg( p_obj,
583              "TIMER %s : Total %.3f ms / %i intvls (Avg %.3f ms)",
584              p_counter->psz_name, (float)total/1000, i_total,
585              (float)(total)/(1000*(float)i_total ) );
586     }
587 }