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 <sys/syscall.h>
 
 /**
  * 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,"<a href='/%d/'>Thread %d</a><br>\n", y, y);
 					send_template(client_socket, res);
 				}
+				sprintf(res,"<a href=/statistics/>Statistics</a>\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,"<center><b>Motion</b> Statistics Page</center>");
+				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,"<br><center>Generated at %s</center>",tmpout);
+				send_template(client_socket, res);
+				sprintf(res,"<br>");
+				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'<br>\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'<br>\n<br>\n", tmpout);
+				send_template(client_socket, res);
+
+				for (y=0; y<i; y++) {
+					if(y==0 && i!=1) /* With 1 thread, data is in cnt[0] so do not 'continue' */
+						{
+						 /* If multiple cam threads, we provide an 'All' hyperlink */
+						sprintf(res,"<a href='/0/'>All</a><br>\n<br>\n");
+						send_template(client_socket, res);
+						continue; /* No further fields for the 'all' line */
+					} else {
+						sprintf(res,"<a href='/%d/'>Thread %d</a>  ", 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'<br>\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,"<a href='/%d/'>Thread %d</a>  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,"'<br>\n");
+					send_template(client_socket, res);
+					sprintf(res,"<a href='/%d/'>Thread %d</a>  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,"<br><br>\n");
+					send_template(client_socket, res);
+				}
+				sprintf(res,"<br>");
+				send_template(client_socket, res);
+				sprintf(res,"<a href=/><- back</a>\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; y<i; y++) { /* In raw text output there's no need for a hyperlink to All */
+					if (cnt[y]->conf.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
