From 7bc5edb00bbd02449576289a50d2900f58e7187a Mon Sep 17 00:00:00 2001 From: Alexey Fisher Date: Thu, 3 Nov 2011 11:40:08 -0300 Subject: [PATCH] [media] uvcvideo: Extract video stream statistics Export the statistics through debugfs. Signed-off-by: Alexey Fisher Signed-off-by: Laurent Pinchart Signed-off-by: Mauro Carvalho Chehab --- drivers/media/video/uvc/uvc_debugfs.c | 63 ++++++++++++++- drivers/media/video/uvc/uvc_video.c | 109 +++++++++++++++++++++++++- drivers/media/video/uvc/uvcvideo.h | 32 +++++++- 3 files changed, 199 insertions(+), 5 deletions(-) diff --git a/drivers/media/video/uvc/uvc_debugfs.c b/drivers/media/video/uvc/uvc_debugfs.c index 39fd43b3e1d2..14561a5abb79 100644 --- a/drivers/media/video/uvc/uvc_debugfs.c +++ b/drivers/media/video/uvc/uvc_debugfs.c @@ -18,6 +18,57 @@ #include "uvcvideo.h" +/* ----------------------------------------------------------------------------- + * Statistics + */ + +#define UVC_DEBUGFS_BUF_SIZE 1024 + +struct uvc_debugfs_buffer { + size_t count; + char data[UVC_DEBUGFS_BUF_SIZE]; +}; + +static int uvc_debugfs_stats_open(struct inode *inode, struct file *file) +{ + struct uvc_streaming *stream = inode->i_private; + struct uvc_debugfs_buffer *buf; + + buf = kmalloc(sizeof(*buf), GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf->count = uvc_video_stats_dump(stream, buf->data, sizeof(buf->data)); + + file->private_data = buf; + return 0; +} + +static ssize_t uvc_debugfs_stats_read(struct file *file, char __user *user_buf, + size_t nbytes, loff_t *ppos) +{ + struct uvc_debugfs_buffer *buf = file->private_data; + + return simple_read_from_buffer(user_buf, nbytes, ppos, buf->data, + buf->count); +} + +static int uvc_debugfs_stats_release(struct inode *inode, struct file *file) +{ + kfree(file->private_data); + file->private_data = NULL; + + return 0; +} + +static const struct file_operations uvc_debugfs_stats_fops = { + .owner = THIS_MODULE, + .open = uvc_debugfs_stats_open, + .llseek = no_llseek, + .read = uvc_debugfs_stats_read, + .release = uvc_debugfs_stats_release, +}; + /* ----------------------------------------------------------------------------- * Global and stream initialization/cleanup */ @@ -37,13 +88,21 @@ int uvc_debugfs_init_stream(struct uvc_streaming *stream) dent = debugfs_create_dir(dir_name, uvc_debugfs_root_dir); if (IS_ERR_OR_NULL(dent)) { - uvc_printk(KERN_INFO, "Unable to create debugfs %s directory.\n", - dir_name); + uvc_printk(KERN_INFO, "Unable to create debugfs %s " + "directory.\n", dir_name); return -ENODEV; } stream->debugfs_dir = dent; + dent = debugfs_create_file("stats", 0444, stream->debugfs_dir, + stream, &uvc_debugfs_stats_fops); + if (IS_ERR_OR_NULL(dent)) { + uvc_printk(KERN_INFO, "Unable to create debugfs stats file.\n"); + uvc_debugfs_cleanup_stream(stream); + return -ENODEV; + } + return 0; } diff --git a/drivers/media/video/uvc/uvc_video.c b/drivers/media/video/uvc/uvc_video.c index a57f81341849..1908dd859433 100644 --- a/drivers/media/video/uvc/uvc_video.c +++ b/drivers/media/video/uvc/uvc_video.c @@ -357,6 +357,100 @@ static int uvc_commit_video(struct uvc_streaming *stream, return uvc_set_video_ctrl(stream, probe, 0); } +/* ------------------------------------------------------------------------ + * Stream statistics + */ + +static void uvc_video_stats_decode(struct uvc_streaming *stream, + const __u8 *data, int len) +{ + unsigned int header_size; + + if (stream->stats.stream.nb_frames == 0 && + stream->stats.frame.nb_packets == 0) + ktime_get_ts(&stream->stats.stream.start_ts); + + switch (data[1] & (UVC_STREAM_PTS | UVC_STREAM_SCR)) { + case UVC_STREAM_PTS | UVC_STREAM_SCR: + header_size = 12; + break; + case UVC_STREAM_PTS: + header_size = 6; + break; + case UVC_STREAM_SCR: + header_size = 8; + break; + default: + header_size = 2; + break; + } + + /* Check for invalid headers. */ + if (len < header_size || data[0] < header_size) { + stream->stats.frame.nb_invalid++; + return; + } + + /* Record the first non-empty packet number. */ + if (stream->stats.frame.size == 0 && len > header_size) + stream->stats.frame.first_data = stream->stats.frame.nb_packets; + + /* Update the frame size. */ + stream->stats.frame.size += len - header_size; + + /* Update the packets counters. */ + stream->stats.frame.nb_packets++; + if (len > header_size) + stream->stats.frame.nb_empty++; + + if (data[1] & UVC_STREAM_ERR) + stream->stats.frame.nb_errors++; +} + +static void uvc_video_stats_update(struct uvc_streaming *stream) +{ + struct uvc_stats_frame *frame = &stream->stats.frame; + + uvc_trace(UVC_TRACE_STATS, "frame %u stats: %u/%u/%u packets\n", + stream->sequence, frame->first_data, + frame->nb_packets - frame->nb_empty, frame->nb_packets); + + stream->stats.stream.nb_frames++; + stream->stats.stream.nb_packets += stream->stats.frame.nb_packets; + stream->stats.stream.nb_empty += stream->stats.frame.nb_empty; + stream->stats.stream.nb_errors += stream->stats.frame.nb_errors; + stream->stats.stream.nb_invalid += stream->stats.frame.nb_invalid; + + memset(&stream->stats.frame, 0, sizeof(stream->stats.frame)); +} + +size_t uvc_video_stats_dump(struct uvc_streaming *stream, char *buf, + size_t size) +{ + size_t count = 0; + + count += scnprintf(buf + count, size - count, + "frames: %u\npackets: %u\nempty: %u\n" + "errors: %u\ninvalid: %u\n", + stream->stats.stream.nb_frames, + stream->stats.stream.nb_packets, + stream->stats.stream.nb_empty, + stream->stats.stream.nb_errors, + stream->stats.stream.nb_invalid); + + return count; +} + +static void uvc_video_stats_start(struct uvc_streaming *stream) +{ + memset(&stream->stats, 0, sizeof(stream->stats)); +} + +static void uvc_video_stats_stop(struct uvc_streaming *stream) +{ + ktime_get_ts(&stream->stats.stream.stop_ts); +} + /* ------------------------------------------------------------------------ * Video codecs */ @@ -406,16 +500,23 @@ static int uvc_video_decode_start(struct uvc_streaming *stream, * - bHeaderLength value must be at least 2 bytes (see above) * - bHeaderLength value can't be larger than the packet size. */ - if (len < 2 || data[0] < 2 || data[0] > len) + if (len < 2 || data[0] < 2 || data[0] > len) { + stream->stats.frame.nb_invalid++; return -EINVAL; + } fid = data[1] & UVC_STREAM_FID; /* Increase the sequence number regardless of any buffer states, so * that discontinuous sequence numbers always indicate lost frames. */ - if (stream->last_fid != fid) + if (stream->last_fid != fid) { stream->sequence++; + if (stream->sequence) + uvc_video_stats_update(stream); + } + + uvc_video_stats_decode(stream, data, len); /* Store the payload FID bit and return immediately when the buffer is * NULL. @@ -860,6 +961,8 @@ static void uvc_uninit_video(struct uvc_streaming *stream, int free_buffers) struct urb *urb; unsigned int i; + uvc_video_stats_stop(stream); + for (i = 0; i < UVC_URBS; ++i) { urb = stream->urb[i]; if (urb == NULL) @@ -999,6 +1102,8 @@ static int uvc_init_video(struct uvc_streaming *stream, gfp_t gfp_flags) stream->bulk.skip_payload = 0; stream->bulk.payload_size = 0; + uvc_video_stats_start(stream); + if (intf->num_altsetting > 1) { struct usb_host_endpoint *best_ep = NULL; unsigned int best_psize = 3 * 1024; diff --git a/drivers/media/video/uvc/uvcvideo.h b/drivers/media/video/uvc/uvcvideo.h index d975636cbb10..f9ee62ed0150 100644 --- a/drivers/media/video/uvc/uvcvideo.h +++ b/drivers/media/video/uvc/uvcvideo.h @@ -356,6 +356,28 @@ struct uvc_video_chain { struct mutex ctrl_mutex; /* Protects ctrl.info */ }; +struct uvc_stats_frame { + unsigned int size; /* Number of bytes captured */ + unsigned int first_data; /* Index of the first non-empty packet */ + + unsigned int nb_packets; /* Number of packets */ + unsigned int nb_empty; /* Number of empty packets */ + unsigned int nb_invalid; /* Number of packets with an invalid header */ + unsigned int nb_errors; /* Number of packets with the error bit set */ +}; + +struct uvc_stats_stream { + struct timespec start_ts; /* Stream start timestamp */ + struct timespec stop_ts; /* Stream stop timestamp */ + + unsigned int nb_frames; /* Number of frames */ + + unsigned int nb_packets; /* Number of packets */ + unsigned int nb_empty; /* Number of empty packets */ + unsigned int nb_invalid; /* Number of packets with an invalid header */ + unsigned int nb_errors; /* Number of packets with the error bit set */ +}; + struct uvc_streaming { struct list_head list; struct uvc_device *dev; @@ -406,6 +428,10 @@ struct uvc_streaming { /* debugfs */ struct dentry *debugfs_dir; + struct { + struct uvc_stats_frame frame; + struct uvc_stats_stream stream; + } stats; }; enum uvc_device_state { @@ -477,6 +503,7 @@ struct uvc_driver { #define UVC_TRACE_SUSPEND (1 << 8) #define UVC_TRACE_STATUS (1 << 9) #define UVC_TRACE_VIDEO (1 << 10) +#define UVC_TRACE_STATS (1 << 11) #define UVC_WARN_MINMAX 0 #define UVC_WARN_PROBE_DEF 1 @@ -609,10 +636,13 @@ extern struct usb_host_endpoint *uvc_find_endpoint( void uvc_video_decode_isight(struct urb *urb, struct uvc_streaming *stream, struct uvc_buffer *buf); -/* debugfs */ +/* debugfs and statistics */ int uvc_debugfs_init(void); void uvc_debugfs_cleanup(void); int uvc_debugfs_init_stream(struct uvc_streaming *stream); void uvc_debugfs_cleanup_stream(struct uvc_streaming *stream); +size_t uvc_video_stats_dump(struct uvc_streaming *stream, char *buf, + size_t size); + #endif