Index: motion.c =================================================================== --- motion.c (revision 171) +++ motion.c (working copy) @@ -21,6 +21,7 @@ #include "event.h" #include "picture.h" #include "rotate.h" +#include /** * tls_key_threadnr @@ -68,6 +69,8 @@ */ int restart=0; +time_t restart_time, program_run_time; /* Times recorded for last restart and when actually started */ + /** * context_init * @@ -100,6 +103,9 @@ cnt->pipe = -1; cnt->mpipe = -1; + cnt->total_diffs=0; + cnt->total_frames=0; + } /** @@ -417,6 +423,8 @@ /* Store thread number in TLS. */ pthread_setspecific(tls_key_threadnr, (void *)((unsigned long)cnt->threadnr)); + syscall(SYS_getpid, cnt->pid); + cnt->diffs = 0; cnt->currenttime_tm = mymalloc(sizeof(struct tm)); cnt->eventtime_tm = mymalloc(sizeof(struct tm)); @@ -672,6 +680,15 @@ int minimum_frame_time_downcounter = cnt->conf.minimum_frame_time; /* time in seconds to skip between capturing images */ int get_image = 1; /* Flag used to signal that we capture new image when we run the loop */ + /* Next variables are for statistics average fps calculation */ + float stats_frame_accum; + float stats_fps; + int stats_rolling_frame = 0; + + /* Initialise the fps average data store to 0 */ + for (j=0; j< STATS_FPS_AVERAGE_CALC_TIME; j++) + cnt->stats_fps_average_data[j]=0; + /* Next two variables are used for snapshot and timelapse feature * time_last_frame is set to 1 so that first coming timelapse or second=0 * is acted upon. @@ -778,9 +795,77 @@ } else get_image = 1; + + /* Once a second Statistics actions */ + + stats_rolling_frame++; + if (stats_rolling_frame >= STATS_FPS_AVERAGE_CALC_TIME) + stats_rolling_frame = 0; + + /* Enter the last second's frame count into the array */ + /* and add the number of frames to the tally, ... but */ + /* only if we have a good connection to camera */ + if (cnt->lost_connection == 0) { + if (cnt->conf.netcam_url) + cnt->stats_fps_average_data[stats_rolling_frame]=(float) 1000000/cnt->netcam->av_frame_time; + else + cnt->stats_fps_average_data[stats_rolling_frame]=(float) cnt->lastrate; + cnt->total_frames += (int) (cnt->stats_fps_average_data[stats_rolling_frame] + 0.5); + } + else + cnt->stats_fps_average_data[stats_rolling_frame]=0; + + /* Calculate average of the elements */ + stats_frame_accum = 0.0; + for (j=0; j < STATS_FPS_AVERAGE_CALC_TIME; j++) + { + stats_frame_accum += cnt->stats_fps_average_data[j]; + } + + stats_fps = (float) stats_frame_accum / STATS_FPS_AVERAGE_CALC_TIME; + cnt->stats_averagefps = stats_fps; + + /* Once every 6 seconds Statistics actions + * Here we work out the 'motion throughput' (see below) + * and if in setup mode, report some useful information to the + * screen, ie. the average camera fps, the motion fps, and + * warn if configured fps not achieved. + */ + + if (cnt->currenttime % 6 == 0) { + + /* Find the 'Motion throughput' every 6 seconds. This is + * defined as the number of frames compared for motion + * which may be less than the fps of the camera, + * on a slow machine. This is because the Jpeg-decompress + * task, colourspace conversion, despeckle and actual + * compare can take a considerable time (in machine + * terms) and the rate of frames compared for motion + * will be less than the received fps of the camera. + * This metric is an attempt to show how much of a speed + * reduction the machine is causing. Causes of this can be: + * raw clock speed ; architecture ; optimization ; etc + * We find it every 6 secs to give better accuracy. + */ + + cnt->stats_motion_thruput = cnt->stats_motion_accum / 6.0 ; + cnt->stats_motion_accum = 0; + + if (cnt->conf.setup_mode) { + char temp[80]; + temp[0]='\0'; + for (j=0; j < STATS_FPS_AVERAGE_CALC_TIME; j++) + { + sprintf(&temp[strlen(temp)],"%.1f ", cnt->stats_fps_average_data[j]); + } + motion_log(LOG_DEBUG, 0, "Calculated average CAMERA fps as %.2f (last %d sec, data '%s').", stats_fps, STATS_FPS_AVERAGE_CALC_TIME, temp); + motion_log(LOG_DEBUG, 0, "Calculated average MOTION fps as %.2f (last 6 sec).", cnt->stats_motion_thruput); + if (cnt->stats_frame_delay == 0) + motion_log(LOG_DEBUG, 0, "Warning: configured frame rate (frame_limit %d) cannot be achieved.", cnt->conf.frame_limit); + } + } } - /* Increase the shots variable for each frame captured within this second */ cnt->shots++; @@ -1012,6 +1097,8 @@ }else if (cnt->imgs.labelsize_max) cnt->imgs.labelsize_max = 0; /* Disable labeling if enabled */ + cnt->stats_motion_accum++; /* Tally motion-detected frames */ + } else if (!cnt->conf.setup_mode) cnt->diffs = 0; @@ -1273,6 +1360,15 @@ } /* get_image end */ + /***** MOTION LOOP - STATISTICS SECTION *****/ + + /* Tally diffs for statistics. */ + cnt->total_diffs+=cnt->diffs; + + /* Count of total frames viewed moved to 'if get_next_frame' success (above) */ + + + /***** MOTION LOOP - SNAPSHOT FEATURE SECTION *****/ /* Did we get triggered to make a snapshot from control http? Then shoot a snap @@ -1488,6 +1584,8 @@ frame_delay = required_frame_time-elapsedtime - (rolling_average - required_frame_time); if (frame_delay > 0) { + cnt->stats_frame_delay = frame_delay; /* For stats reporting */ + /* Apply delay to meet frame time */ if (frame_delay > required_frame_time) frame_delay = required_frame_time; @@ -1500,6 +1598,10 @@ /* SLEEP as defined in motion.h A safe sleep using nanosleep */ SLEEP(0, delay_time_nsec); + + } else { + /* There was no delay time needed, or more likely it was negative. */ + cnt->stats_frame_delay = 0; } /* This will limit the framerate to 1 frame while not detecting @@ -1860,6 +1962,10 @@ /* Create the TLS key for thread number. */ pthread_key_create(&tls_key_threadnr, NULL); + /* Record the actual program start time (record restart time later) */ + program_run_time=time(NULL); + restart_time=time(NULL); /* Set this too, if first time through */ + do { if (restart) { /* Handle the restart situation. Currently the approach is to @@ -1872,6 +1978,9 @@ #ifndef WITHOUT_V4L SLEEP(5,0); // maybe some cameras needs less time #endif + /* Record the most recent restart time */ + restart_time=time(NULL); + motion_startup(0, argc, argv); /* 0 = skip daemon init */ } Index: motion.h =================================================================== --- motion.h (revision 171) +++ motion.h (working copy) @@ -137,6 +137,8 @@ #define DEF_TIMESTAMP "%Y-%m-%d\\n%T" #define DEF_EVENTSTAMP "%Y%m%d%H%M%S" +#define STATS_FPS_AVERAGE_CALC_TIME 10 /* 10 secs of averaging */ + #define DEF_SNAPPATH "%v-%Y%m%d%H%M%S-snapshot" #define DEF_JPEGPATH "%v-%Y%m%d%H%M%S-%q" #define DEF_MPEGPATH "%v-%Y%m%d%H%M%S" @@ -302,6 +304,19 @@ int missing_frame_counter; /* counts failed attempts to fetch picture frame from camera */ int lost_connection; + unsigned long total_diffs; /* for statistics, how many pixels different so far? */ + unsigned long total_frames; /* for statistics, how many frames seen so far? */ + /* Next we reserve 1 more data store to hold the end of the 10th second */ + float stats_fps_average_data[1 + STATS_FPS_AVERAGE_CALC_TIME]; + float stats_averagefps; /* for statistics, average camera fps over time */ + float stats_motion_thruput; /* average motion fps over time */ + int stats_motion_accum; /* accumulator for above */ + long int stats_frame_delay; /* for illustration of idle time in thread */ + int v4l2_dev; + + unsigned int pid; /* for statistics, our pid. */ + + #if (defined(BSD)) int tuner_dev; #endif Index: video_common.c =================================================================== --- video_common.c (revision 171) +++ video_common.c (working copy) @@ -715,6 +715,8 @@ cnt->imgs.height = height; } + cnt->v4l2_dev = viddevs[i]->v4l2; + cnt->imgs.type = viddevs[i]->v4l_fmt; switch (cnt->imgs.type) { Index: webhttpd.c =================================================================== --- webhttpd.c (revision 171) +++ webhttpd.c (working copy) @@ -1685,6 +1685,13 @@ static int handle_get(int client_socket, const char* url, void *userdata) { struct context **cnt=userdata; + char typestring[12]; /* Long enough for 'Video4Linux' plus \0 */ + char tmpin1[] = "%Y-%m-%d %T"; /* Format string in Stats page 'Generated' and start times */ + char tmpin2[] = "%Y-%m-%d %T"; /* Format string in ConnLost line, same as on grey image */ + char tmpout[80]; /* Contains result of mystrftime with above inputs */ + struct tm tmptime; + float req_rate; /* Calculated taking into account minimum_frame_time */ + if (*url == '/' ){ int i=0; char *res=NULL; @@ -1704,6 +1711,8 @@ sprintf(res,"Thread %d
\n", y, y); send_template(client_socket, res); } + sprintf(res,"Statistics\n"); + send_template(client_socket, res); send_template_end_client(client_socket); } else { send_template_ini_client_raw(client_socket); @@ -1713,8 +1722,165 @@ sprintf(res, "%d\n", y); send_template_raw(client_socket, res); } + sprintf(res,"Statistics\n"); + send_template_raw(client_socket, res); } } + else if (! (strcmp (url, "/statistics/")) ) { + int y=0; + int j; + if (cnt[0]->conf.control_html_output) { + send_template_ini_client(client_socket,ini_template); + sprintf(res,"
Motion Statistics Page
"); + send_template(client_socket, res); + if (i==1) + localtime_r(&cnt[0]->currenttime, &tmptime); + else + localtime_r(&cnt[1]->currenttime, &tmptime); + mystrftime(cnt[0], tmpout, sizeof(tmpout), tmpin1, &tmptime, NULL, 0); + sprintf(res,"
Generated at %s
",tmpout); + send_template(client_socket, res); + sprintf(res,"
"); + send_template(client_socket, res); + + localtime_r(&program_run_time, &tmptime); + mystrftime(cnt[0], tmpout, sizeof(tmpout), tmpin1, &tmptime, NULL, 0); + sprintf(res,"MotionStart='%s'
\n", tmpout); + send_template(client_socket, res); + localtime_r(&restart_time, &tmptime); + mystrftime(cnt[0], tmpout, sizeof(tmpout), tmpin1, &tmptime, NULL, 0); + sprintf(res,"LastRestart='%s'
\n
\n", tmpout); + send_template(client_socket, res); + + for (y=0; yAll
\n
\n"); + send_template(client_socket, res); + continue; /* No further fields for the 'all' line */ + } else { + sprintf(res,"Thread %d ", y, y); + send_template(client_socket, res); + } + strcpy(tmpout,cnt[y]->conf.text_event); + /* motion_log(LOG_DEBUG, 0, "webhttpd: text_event='%s'",tmpout); */ + sprintf(res," \"%s\" ",tmpout); + send_template(client_socket, res); + if (cnt[y]->conf.netcam_url) { + sprintf(res,"(Netcam) "); + send_template(client_socket, res); +#ifdef MOTION_V4L2 + } else if (!cnt[y]->v4l2_dev) { + sprintf(res,"(V4L1) "); + send_template(client_socket, res); + } else if (cnt[y]->v4l2_dev) { + sprintf(res,"(V4L2) "); + send_template(client_socket, res); +#endif + +#ifndef WITHOUT_V4L + } else if (cnt[y]->video_dev != -1) { + sprintf(res,"(V4L1/2) "); + send_template(client_socket, res); +#endif + } else { + sprintf(res,"(None) "); + send_template(client_socket, res); + } + if (cnt[y]->event_nr-1 == 0) /* No events yet, print a sensible time */ + strcpy(tmpout,"None"); + else + mystrftime(cnt[y], tmpout, sizeof(tmpout), tmpin1, cnt[y]->eventtime_tm, NULL, 0); + sprintf(res,"Events=%d LastEventTime='%s'
\n", cnt[y]->event_nr-1, tmpout); + send_template(client_socket, res); + if (cnt[y]->conf.minimum_frame_time > 0) + req_rate=1.0/cnt[y]->conf.minimum_frame_time; + else + req_rate=cnt[y]->conf.frame_limit; + sprintf(res,"Thread %d Frames=%lu fps=%.2f Lastrate=%d Req.Rate=%.1f FrameDelay[ms]=%.3f fps[10s]='", y, y, cnt[y]->total_frames, cnt[y]->stats_averagefps, cnt[y]->lastrate, req_rate, cnt[y]->stats_frame_delay/1000.0); + send_template(client_socket, res); + + for (j=0; j < STATS_FPS_AVERAGE_CALC_TIME ; j++){ + sprintf(res,"%.1f ", cnt[y]->stats_fps_average_data[j] ); + send_template(client_socket, res); + } + + sprintf(res,"'
\n"); + send_template(client_socket, res); + sprintf(res,"Thread %d MotionThruPut=%.1f Diff=%d Thresh=%d Total=%lu Pid=%u MissFrameCtr=%d ", y, y, cnt[y]->stats_motion_thruput, cnt[y]->diffs, cnt[y]->threshold, cnt[y]->total_diffs, cnt[y]->pid, cnt[y]->missing_frame_counter); + send_template(client_socket, res); + sprintf(res,"ConnLost="); + send_template(client_socket, res); + if (cnt[y]->lost_connection) { + localtime_r(&cnt[y]->connectionlosttime, &tmptime); + mystrftime(cnt[y], tmpout, sizeof(tmpout), tmpin2, &tmptime, NULL, 0); + sprintf(res,"'%s'", tmpout); + } else { + sprintf(res,"'No'"); + } + send_template(client_socket, res); + sprintf(res,"

\n"); + send_template(client_socket, res); + } + sprintf(res,"
"); + send_template(client_socket, res); + sprintf(res,"<- back\n"); + send_template(client_socket, res); + send_template_end_client(client_socket); + } else { + /* TODO: Raw output currently is not in the re-arranged format. */ + send_template_ini_client_raw(client_socket); + sprintf(res,"Motion Statistics Page\n\n"); + send_template_raw(client_socket, res); + localtime_r(&cnt[1]->currenttime, &tmptime); + mystrftime(cnt[1], tmpout, sizeof(tmpout), tmpin1, &tmptime, NULL, 0); + sprintf(res,"Generated at %s\n\n",tmpout); + send_template_raw(client_socket, res); + for (y=1; yconf.netcam_url) { + strcpy(typestring,"Netcam"); +#ifdef MOTION_V4L2 + } else if (cnt[y]->v4l2_dev) { + strcpy(typestring,"V4L2"); + } else if (!cnt[y]->v4l2_dev) { + strcpy(typestring,"V4L1"); +#endif +#ifndef WITHOUT_V4L + } else if (cnt[y]->video_dev!=-1) { + strcpy(typestring,"V4L"); +#endif + } else { + strcpy(typestring,"None"); + } + + strcpy(tmpout,cnt[y]->conf.text_event); + /* motion_log(LOG_DEBUG, 0, "webhttpd: text_event='%s'",tmpout); */ + sprintf(res,"%d \"%s\" (%s) Events=%d\n", y, tmpout, typestring, cnt[y]->event_nr-1); + send_template_raw(client_socket, res); + if (cnt[y]->conf.minimum_frame_time > 0) + req_rate=1.0/cnt[y]->conf.minimum_frame_time; + else + req_rate=cnt[y]->conf.frame_limit; + sprintf(res, "%d fps=%.2f Lastrate=%d Req.Rate=%.1f FrameDelay[ms]=%.3f Frames=%lu\n", y, cnt[y]->stats_averagefps, cnt[y]->lastrate, req_rate, cnt[y]->stats_frame_delay/1000.0, cnt[y]->total_frames); + send_template_raw(client_socket, res); + sprintf(res, "%d Diff=%d Thresh=%d Total=%lu Pid=%u MissFrameCntr=%d ", y, cnt[y]->diffs, cnt[y]->threshold, cnt[y]->total_diffs, cnt[y]->pid, cnt[y]->missing_frame_counter); + send_template_raw(client_socket, res); + sprintf(res,"ConnLost="); + send_template_raw(client_socket, res); + if (cnt[y]->lost_connection) { + localtime_r(&cnt[y]->connectionlosttime, &tmptime); + mystrftime(cnt[y], tmpout, sizeof(tmpout), tmpin2, &tmptime, NULL, 0); + sprintf(res,"'%s'", tmpout); + } else { + sprintf(res,"'No'"); + } + send_template_raw(client_socket, res); + sprintf(res,"\n\n"); + send_template_raw(client_socket, res); + } + } + } else { char command[256] = {'\0'}; char slash; Index: webhttpd.h =================================================================== --- webhttpd.h (revision 171) +++ webhttpd.h (working copy) @@ -16,4 +16,6 @@ void * motion_web_control(void *arg); void httpd_run(struct context **); +extern time_t program_run_time, restart_time; + #endif