Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
timeshift: implement timeshift to RAM, fixes #2626
  • Loading branch information
perexg committed Jan 19, 2015
1 parent ca0021e commit bc9874c
Show file tree
Hide file tree
Showing 9 changed files with 312 additions and 131 deletions.
5 changes: 5 additions & 0 deletions docs/html/config_timeshift.html
Expand Up @@ -36,6 +36,11 @@
specify an unlimited period its highly recommended you specifying a value
here.

<dt>Max. RAM Size (MegaBytes)
<dd>Specifies the maximum RAM (system memory) size for timeshift buffers.
When free RAM buffers are available, they are used instead storage to
save the timeshift data.

<dt>Unlimited:
<dd>If checked, this allows the combined size of all timeshift buffers to
potentially grow unbounded until your storage media runs out of space
Expand Down
9 changes: 9 additions & 0 deletions src/timeshift.c
Expand Up @@ -41,6 +41,8 @@ int timeshift_unlimited_period;
uint32_t timeshift_max_period;
int timeshift_unlimited_size;
uint64_t timeshift_max_size;
uint64_t timeshift_ram_size;
uint64_t timeshift_ram_segment_size;

/*
* Intialise global file manager
Expand All @@ -61,6 +63,8 @@ void timeshift_init ( void )
timeshift_max_period = 3600; // 1Hr
timeshift_unlimited_size = 0;
timeshift_max_size = 10000 * (size_t)1048576; // 10G
timeshift_ram_size = 0;
timeshift_ram_segment_size = 0;

/* Load settings */
if ((m = hts_settings_load("timeshift/config"))) {
Expand All @@ -77,6 +81,10 @@ void timeshift_init ( void )
timeshift_unlimited_size = u32 ? 1 : 0;
if (!htsmsg_get_u32(m, "max_size", &u32))
timeshift_max_size = 1048576LL * u32;
if (!htsmsg_get_u32(m, "ram_size", &u32)) {
timeshift_ram_size = 1048576LL * u32;
timeshift_ram_segment_size = timeshift_ram_size / 10;
}
htsmsg_destroy(m);
}
}
Expand Down Expand Up @@ -107,6 +115,7 @@ void timeshift_save ( void )
htsmsg_add_u32(m, "max_period", timeshift_max_period);
htsmsg_add_u32(m, "unlimited_size", timeshift_unlimited_size);
htsmsg_add_u32(m, "max_size", timeshift_max_size / 1048576);
htsmsg_add_u32(m, "ram_size", timeshift_ram_size / 1048576);

hts_settings_save(m, "timeshift/config");
}
Expand Down
3 changes: 3 additions & 0 deletions src/timeshift.h
Expand Up @@ -27,6 +27,9 @@ extern uint32_t timeshift_max_period;
extern int timeshift_unlimited_size;
extern uint64_t timeshift_max_size;
extern uint64_t timeshift_total_size;
extern uint64_t timeshift_ram_size;
extern uint64_t timeshift_ram_segment_size;
extern uint64_t timeshift_total_ram_size;

typedef struct timeshift_status
{
Expand Down
20 changes: 14 additions & 6 deletions src/timeshift/private.h
Expand Up @@ -51,12 +51,18 @@ typedef TAILQ_HEAD(timeshift_index_data_list,timeshift_index_data) timeshift_ind
*/
typedef struct timeshift_file
{
int fd; ///< Write descriptor
int wfd; ///< Write descriptor
int rfd; ///< Read descriptor
char *path; ///< Full path to file

time_t time; ///< Files coarse timestamp
size_t size; ///< Current file size;
int64_t last; ///< Latest timestamp
off_t woff; ///< Write offset
off_t roff; ///< Read offset

uint8_t *ram; ///< RAM area
int64_t ram_size; ///< RAM area size in bytes

uint8_t bad; ///< File is broken

Expand All @@ -66,6 +72,8 @@ typedef struct timeshift_file
timeshift_index_data_list_t sstart; ///< Stream start messages

TAILQ_ENTRY(timeshift_file) link; ///< List entry

pthread_mutex_t ram_lock; ///< Mutex for the ram array access
} timeshift_file_t;

typedef TAILQ_HEAD(timeshift_file_list,timeshift_file) timeshift_file_list_t;
Expand Down Expand Up @@ -113,15 +121,15 @@ typedef struct timeshift {
/*
* Write functions
*/
ssize_t timeshift_write_start ( int fd, int64_t time, streaming_start_t *ss );
ssize_t timeshift_write_sigstat ( int fd, int64_t time, signal_status_t *ss );
ssize_t timeshift_write_packet ( int fd, int64_t time, th_pkt_t *pkt );
ssize_t timeshift_write_mpegts ( int fd, int64_t time, void *data );
ssize_t timeshift_write_start ( timeshift_file_t *tsf, int64_t time, streaming_start_t *ss );
ssize_t timeshift_write_sigstat ( timeshift_file_t *tsf, int64_t time, signal_status_t *ss );
ssize_t timeshift_write_packet ( timeshift_file_t *tsf, int64_t time, th_pkt_t *pkt );
ssize_t timeshift_write_mpegts ( timeshift_file_t *tsf, int64_t time, void *data );
ssize_t timeshift_write_skip ( int fd, streaming_skip_t *skip );
ssize_t timeshift_write_speed ( int fd, int speed );
ssize_t timeshift_write_stop ( int fd, int code );
ssize_t timeshift_write_exit ( int fd );
ssize_t timeshift_write_eof ( int fd );
ssize_t timeshift_write_eof ( timeshift_file_t *tsf );

void timeshift_writer_flush ( timeshift_t *ts );

Expand Down
138 changes: 98 additions & 40 deletions src/timeshift/timeshift_filemgr.c
Expand Up @@ -39,6 +39,7 @@ static pthread_mutex_t timeshift_reaper_lock;
static pthread_cond_t timeshift_reaper_cond;

uint64_t timeshift_total_size;
uint64_t timeshift_total_ram_size;

/* **************************************************************************
* File reaper thread
Expand All @@ -63,15 +64,19 @@ static void* timeshift_reaper_callback ( void *p )
TAILQ_REMOVE(&timeshift_reaper_list, tsf, link);
pthread_mutex_unlock(&timeshift_reaper_lock);

tvhtrace("timeshift", "remove file %s", tsf->path);

/* Remove */
unlink(tsf->path);
dpath = dirname(tsf->path);
if (rmdir(dpath) == -1)
if (errno != ENOTEMPTY)
tvhlog(LOG_ERR, "timeshift", "failed to remove %s [e=%s]",
dpath, strerror(errno));
if (tsf->path) {
tvhtrace("timeshift", "remove file %s", tsf->path);

/* Remove */
unlink(tsf->path);
dpath = dirname(tsf->path);
if (rmdir(dpath) == -1)
if (errno != ENOTEMPTY)
tvhlog(LOG_ERR, "timeshift", "failed to remove %s [e=%s]",
dpath, strerror(errno));
} else {
tvhtrace("timeshift", "remove RAM segment (time %li)", (long)tsf->time);
}

/* Free memory */
while ((ti = TAILQ_FIRST(&tsf->iframes))) {
Expand All @@ -85,6 +90,7 @@ static void* timeshift_reaper_callback ( void *p )
free(tid);
}
free(tsf->path);
free(tsf->ram);
free(tsf);

pthread_mutex_lock(&timeshift_reaper_lock);
Expand All @@ -96,7 +102,12 @@ static void* timeshift_reaper_callback ( void *p )

static void timeshift_reaper_remove ( timeshift_file_t *tsf )
{
tvhtrace("timeshift", "queue file for removal %s", tsf->path);
#if ENABLE_TRACE
if (tsf->path)
tvhtrace("timeshift", "queue file for removal %s", tsf->path);
else
tvhtrace("timeshift", "queue file for removal - RAM segment time %li", (long)tsf->time);
#endif
pthread_mutex_lock(&timeshift_reaper_lock);
TAILQ_INSERT_TAIL(&timeshift_reaper_list, tsf, link);
pthread_cond_signal(&timeshift_reaper_cond);
Expand Down Expand Up @@ -140,14 +151,17 @@ int timeshift_filemgr_makedirs ( int index, char *buf, size_t len )
*/
void timeshift_filemgr_close ( timeshift_file_t *tsf )
{
ssize_t r = timeshift_write_eof(tsf->fd);
ssize_t r = timeshift_write_eof(tsf);
if (r > 0)
{
tsf->size += r;
atomic_add_u64(&timeshift_total_size, r);
if (tsf->ram)
atomic_add_u64(&timeshift_total_ram_size, r);
}
close(tsf->fd);
tsf->fd = -1;
if (tsf->wfd >= 0)
close(tsf->wfd);
tsf->wfd = -1;
}

/*
Expand All @@ -156,11 +170,19 @@ void timeshift_filemgr_close ( timeshift_file_t *tsf )
void timeshift_filemgr_remove
( timeshift_t *ts, timeshift_file_t *tsf, int force )
{
if (tsf->fd != -1)
close(tsf->fd);
tvhlog(LOG_DEBUG, "timeshift", "ts %d remove %s", ts->id, tsf->path);
if (tsf->wfd >= 0)
close(tsf->wfd);
assert(tsf->rfd < 0);
#if ENABLE_TRACE
if (tsf->path)
tvhdebug("timeshift", "ts %d remove %s", ts->id, tsf->path);
else
tvhdebug("timeshift", "ts %d RAM segment remove time %li", ts->id, (long)tsf->time);
#endif
TAILQ_REMOVE(&ts->files, tsf, link);
atomic_add_u64(&timeshift_total_size, -tsf->size);
if (tsf->ram)
atomic_add_u64(&timeshift_total_ram_size, -tsf->size);
timeshift_reaper_remove(tsf);
}

Expand All @@ -176,6 +198,26 @@ void timeshift_filemgr_flush ( timeshift_t *ts, timeshift_file_t *end )
}
}

/*
*
*/
static timeshift_file_t * timeshift_filemgr_file_init
( timeshift_t *ts, time_t time )
{
timeshift_file_t *tsf;

tsf = calloc(1, sizeof(timeshift_file_t));
tsf->time = time;
tsf->last = getmonoclock();
tsf->wfd = -1;
tsf->rfd = -1;
TAILQ_INIT(&tsf->iframes);
TAILQ_INIT(&tsf->sstart);
TAILQ_INSERT_TAIL(&ts->files, tsf, link);
pthread_mutex_init(&tsf->ram_lock, NULL);
return tsf;
}

/*
* Get current / new file
*/
Expand All @@ -185,7 +227,7 @@ timeshift_file_t *timeshift_filemgr_get ( timeshift_t *ts, int create )
struct timespec tp;
timeshift_file_t *tsf_tl, *tsf_hd, *tsf_tmp;
timeshift_index_data_t *ti;
char path[512];
char path[PATH_MAX];
time_t time;

/* Return last file */
Expand All @@ -200,11 +242,12 @@ timeshift_file_t *timeshift_filemgr_get ( timeshift_t *ts, int create )
clock_gettime(CLOCK_MONOTONIC_COARSE, &tp);
time = tp.tv_sec / TIMESHIFT_FILE_PERIOD;
tsf_tl = TAILQ_LAST(&ts->files, timeshift_file_list);
if (!tsf_tl || tsf_tl->time != time) {
if (!tsf_tl || tsf_tl->time != time ||
(tsf_tl->ram && tsf_tl->woff >= timeshift_ram_segment_size)) {
tsf_hd = TAILQ_FIRST(&ts->files);

/* Close existing */
if (tsf_tl && tsf_tl->fd != -1)
if (tsf_tl)
timeshift_filemgr_close(tsf_tl);

/* Check period */
Expand Down Expand Up @@ -236,32 +279,48 @@ timeshift_file_t *timeshift_filemgr_get ( timeshift_t *ts, int create )
ts->full = 1;
}
}

/* Create new file */
tsf_tmp = NULL;
if (!ts->full) {

/* Create directories */
if (!ts->path) {
if (timeshift_filemgr_makedirs(ts->id, path, sizeof(path)))
return NULL;
ts->path = strdup(path);
tvhtrace("timeshift", "ts %d RAM total %"PRId64" requested %"PRId64" segment %"PRId64,
ts->id, atomic_pre_add_u64(&timeshift_total_ram_size, 0),
timeshift_ram_size, timeshift_ram_segment_size);
if (timeshift_ram_size >= 8*1024*1024 &&
atomic_pre_add_u64(&timeshift_total_ram_size, 0) <
timeshift_ram_size + (timeshift_ram_segment_size / 2)) {
tsf_tmp = timeshift_filemgr_file_init(ts, time);
tsf_tmp->ram_size = MIN(16*1024*1024, timeshift_ram_segment_size);
tsf_tmp->ram = malloc(tsf_tmp->ram_size);
if (!tsf_tmp->ram) {
free(tsf_tmp);
tsf_tmp = NULL;
} else {
tvhtrace("timeshift", "ts %d create RAM segment with %"PRId64" bytes (time %li)",
ts->id, tsf_tmp->ram_size, (long)time);
}
}

if (!tsf_tmp) {
/* Create directories */
if (!ts->path) {
if (timeshift_filemgr_makedirs(ts->id, path, sizeof(path)))
return NULL;
ts->path = strdup(path);
}

/* Create File */
snprintf(path, sizeof(path), "%s/tvh-%"PRItime_t, ts->path, time);
tvhtrace("timeshift", "ts %d create file %s", ts->id, path);
if ((fd = open(path, O_WRONLY | O_CREAT, 0600)) > 0) {
tsf_tmp = calloc(1, sizeof(timeshift_file_t));
tsf_tmp->time = time;
tsf_tmp->fd = fd;
tsf_tmp->path = strdup(path);
tsf_tmp->refcount = 0;
tsf_tmp->last = getmonoclock();
TAILQ_INIT(&tsf_tmp->iframes);
TAILQ_INIT(&tsf_tmp->sstart);
TAILQ_INSERT_TAIL(&ts->files, tsf_tmp, link);
/* Create File */
snprintf(path, sizeof(path), "%s/tvh-%"PRItime_t, ts->path, time);
tvhtrace("timeshift", "ts %d create file %s", ts->id, path);
if ((fd = open(path, O_WRONLY | O_CREAT, 0600)) > 0) {
tsf_tmp = timeshift_filemgr_file_init(ts, time);
tsf_tmp->wfd = fd;
tsf_tmp->path = strdup(path);
}
}

if (tsf_tmp) {
/* Copy across last start message */
if (tsf_tl && (ti = TAILQ_LAST(&tsf_tl->sstart, timeshift_index_data_list))) {
tvhtrace("timeshift", "ts %d copy smt_start to new file",
Expand Down Expand Up @@ -343,6 +402,7 @@ void timeshift_filemgr_init ( void )

/* Size processing */
timeshift_total_size = 0;
timeshift_ram_size = 0;

/* Start the reaper thread */
timeshift_reaper_run = 1;
Expand Down Expand Up @@ -371,5 +431,3 @@ void timeshift_filemgr_term ( void )
if (!timeshift_filemgr_get_root(path, sizeof(path)))
rmtree(path);
}


1 comment on commit bc9874c

@ksooo
Copy link
Contributor

@ksooo ksooo commented on bc9874c Jan 21, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this commit I experience hangs when skipping/seeking to/past the "ends" of the timeshift buffer. Tried with 1GB RAM and not "only RAM".

Please sign in to comment.