MythTV  master
eitcache.cpp
Go to the documentation of this file.
1 // -*- Mode: c++ -*-
2 /*
3  * Copyright 2006 (C) Stuart Auchterlonie <stuarta at squashedfrog.net>
4  * Copyright 2006 (C) Janne Grunau <janne-mythtv at grunau.be>
5  * License: GPL v2
6  */
7 
8 #include <QDateTime>
9 
10 #include "libmyth/mythcontext.h"
11 #include "libmythbase/mythdate.h"
12 #include "libmythbase/mythdb.h"
14 
15 #include "eitcache.h"
16 
17 #define LOC QString("EITCache: ")
18 
19 // Highest version number. version is 5bits
20 const uint EITCache::kVersionMax = 31;
21 
23 {
24  // 24 hours ago
25  m_lastPruneTime = MythDate::current().toUTC().toSecsSinceEpoch() - 86400;
26 }
27 
29 {
30  WriteToDB();
31 }
32 
34 {
35  m_accessCnt = 0;
36  m_hitCnt = 0;
37  m_tblChgCnt = 0;
38  m_verChgCnt = 0;
39  m_endChgCnt = 0;
40  m_entryCnt = 0;
41  m_pruneCnt = 0;
42  m_prunedHitCnt = 0;
43  m_futureHitCnt = 0;
45 }
46 
47 QString EITCache::GetStatistics(void) const
48 {
49  QMutexLocker locker(&m_eventMapLock);
50  return
51  QString("Access:%1 ").arg(m_accessCnt) +
52  QString("HitRatio:%1 ").arg((m_hitCnt+m_prunedHitCnt+m_futureHitCnt+m_wrongChannelHitCnt)/(double)m_accessCnt) +
53  QString("Hits:%1 ").arg(m_hitCnt) +
54  QString("Table:%1 ").arg(m_tblChgCnt) +
55  QString("Version:%1 ").arg(m_verChgCnt) +
56  QString("Endtime:%1 ").arg(m_endChgCnt) +
57  QString("New:%1 ").arg(m_entryCnt) +
58  QString("Pruned:%1 ").arg(m_pruneCnt) +
59  QString("PrunedHits:%1 ").arg(m_prunedHitCnt) +
60  QString("Future:%1 ").arg(m_futureHitCnt) +
61  QString("WrongChannel:%1").arg(m_wrongChannelHitCnt);
62 }
63 
64 /*
65  * FIXME: This code has a builtin assumption that all timestamps will
66  * fit into a 32bit integer. Qt5.8 has switched to using a 64bit
67  * integer for timestamps.
68  */
69 static inline uint64_t construct_sig(uint tableid, uint version,
70  uint endtime, bool modified)
71 {
72  return (((uint64_t) modified << 63) | ((uint64_t) tableid << 40) |
73  ((uint64_t) version << 32) | ((uint64_t) endtime));
74 }
75 
76 static inline uint extract_table_id(uint64_t sig)
77 {
78  return (sig >> 40) & 0xff;
79 }
80 
81 static inline uint extract_version(uint64_t sig)
82 {
83  return (sig >> 32) & 0x1f;
84 }
85 
86 static inline uint extract_endtime(uint64_t sig)
87 {
88  return sig & 0xffffffff;
89 }
90 
91 static inline bool modified(uint64_t sig)
92 {
93  return (sig >> 63) != 0U;
94 }
95 
96 static void replace_in_db(QStringList &value_clauses,
97  uint chanid, uint eventid, uint64_t sig)
98 {
99  value_clauses << QString("(%1,%2,%3,%4,%5)")
100  .arg(chanid).arg(eventid).arg(extract_table_id(sig))
101  .arg(extract_version(sig)).arg(extract_endtime(sig));
102 }
103 
104 static void delete_in_db(uint endtime)
105 {
106  LOG(VB_EIT, LOG_INFO, LOC + "Deleting old cache entries from the database");
107  MSqlQuery query(MSqlQuery::InitCon());
108 
109  QString qstr =
110  "DELETE FROM eit_cache "
111  "WHERE endtime < :ENDTIME";
112 
113  query.prepare(qstr);
114  query.bindValue(":ENDTIME", endtime);
115 
116  if (!query.exec())
117  MythDB::DBError("Error deleting old eitcache entries.", query);
118 }
119 
121 {
122  EITDATA = 0,
125 };
126 
127 static bool lock_channel(uint chanid, uint endtime)
128 {
129  int lock = 1;
130  MSqlQuery query(MSqlQuery::InitCon());
131 
132  QString qstr = "SELECT COUNT(*) "
133  "FROM eit_cache "
134  "WHERE chanid = :CHANID AND "
135  " endtime > :ENDTIME AND "
136  " status = :STATUS";
137 
138  query.prepare(qstr);
139  query.bindValue(":CHANID", chanid);
140  query.bindValue(":ENDTIME", endtime);
141  query.bindValue(":STATUS", CHANNEL_LOCK);
142 
143  if (!query.exec() || !query.isActive())
144  {
145  MythDB::DBError("Error checking for channel lock", query);
146  return false;
147  }
148 
149  if (query.next())
150  lock = query.value(0).toInt();
151 
152  if (lock)
153  {
154  LOG(VB_EIT, LOG_INFO,
155  LOC + QString("Ignoring channel %1 since it is locked.")
156  .arg(chanid));
157  return false;
158  }
159  uint now = MythDate::current().toSecsSinceEpoch();
160  qstr = "INSERT INTO eit_cache "
161  " ( chanid, endtime, status) "
162  "VALUES (:CHANID, :ENDTIME, :STATUS)";
163 
164  query.prepare(qstr);
165  query.bindValue(":CHANID", chanid);
166  query.bindValue(":ENDTIME", now);
167  query.bindValue(":STATUS", CHANNEL_LOCK);
168 
169  if (!query.exec())
170  {
171  MythDB::DBError("Error inserting channel lock", query);
172  return false;
173  }
174 
175  return true;
176 }
177 
178 static void unlock_channel(uint chanid, uint updated)
179 {
180  MSqlQuery query(MSqlQuery::InitCon());
181 
182  QString qstr =
183  "DELETE FROM eit_cache "
184  "WHERE chanid = :CHANID AND "
185  " status = :STATUS";
186 
187  query.prepare(qstr);
188  query.bindValue(":CHANID", chanid);
189  query.bindValue(":STATUS", CHANNEL_LOCK);
190 
191  if (!query.exec())
192  MythDB::DBError("Error deleting channel lock", query);
193 
194  // inserting statistics
195  uint now = MythDate::current().toSecsSinceEpoch();
196  qstr = "REPLACE INTO eit_cache "
197  " ( chanid, eventid, endtime, status) "
198  "VALUES (:CHANID, :EVENTID, :ENDTIME, :STATUS)";
199 
200  query.prepare(qstr);
201  query.bindValue(":CHANID", chanid);
202  query.bindValue(":EVENTID", updated);
203  query.bindValue(":ENDTIME", now);
204  query.bindValue(":STATUS", STATISTIC);
205 
206  if (!query.exec())
207  MythDB::DBError("Error inserting eit statistics", query);
208 }
209 
211 {
212  // Event map is empty when we do not backup the cache in the database
213  if (!m_persistent)
214  {
215  return new event_map_t();
216  }
217 
218  if (!lock_channel(chanid, m_lastPruneTime))
219  return nullptr;
220 
221  MSqlQuery query(MSqlQuery::InitCon());
222 
223  QString qstr =
224  "SELECT eventid,tableid,version,endtime "
225  "FROM eit_cache "
226  "WHERE chanid = :CHANID AND "
227  " endtime > :ENDTIME AND "
228  " status = :STATUS";
229 
230  query.prepare(qstr);
231  query.bindValue(":CHANID", chanid);
232  query.bindValue(":ENDTIME", m_lastPruneTime);
233  query.bindValue(":STATUS", EITDATA);
234 
235  if (!query.exec() || !query.isActive())
236  {
237  MythDB::DBError("Error loading eitcache", query);
238  return nullptr;
239  }
240 
241  auto *eventMap = new event_map_t();
242 
243  while (query.next())
244  {
245  uint eventid = query.value(0).toUInt();
246  uint tableid = query.value(1).toUInt();
247  uint version = query.value(2).toUInt();
248  uint endtime = query.value(3).toUInt();
249 
250  (*eventMap)[eventid] = construct_sig(tableid, version, endtime, false);
251  }
252 
253  if (!eventMap->empty())
254  LOG(VB_EIT, LOG_DEBUG, LOC + QString("Loaded %1 entries for chanid %2")
255  .arg(eventMap->size()).arg(chanid));
256 
257  m_entryCnt += eventMap->size();
258  return eventMap;
259 }
260 
261 bool EITCache::WriteChannelToDB(QStringList &value_clauses, uint chanid)
262 {
263  event_map_t * eventMap = m_channelMap[chanid];
264 
265  if (!eventMap)
266  return false;
267 
268  uint size = eventMap->size();
269  uint updated = 0;
270  uint removed = 0;
271 
272  event_map_t::iterator it = eventMap->begin();
273  while (it != eventMap->end())
274  {
275  if (extract_endtime(*it) > m_lastPruneTime)
276  {
277  if (modified(*it))
278  {
279  if (m_persistent)
280  {
281  replace_in_db(value_clauses, chanid, it.key(), *it);
282  }
283 
284  updated++;
285  *it &= ~(uint64_t)0 >> 1; // Mark as synced
286  }
287  ++it;
288  }
289  else
290  {
291  // Event is too old; remove from eit cache in memory
292  it = eventMap->erase(it);
293  removed++;
294  }
295  }
296 
297  if (m_persistent)
298  {
299  unlock_channel(chanid, updated);
300  }
301 
302  if (updated)
303  {
304  if (m_persistent)
305  {
306  LOG(VB_EIT, LOG_DEBUG, LOC +
307  QString("Writing %1 modified entries of %2 for chanid %3 to database.")
308  .arg(updated).arg(size).arg(chanid));
309  }
310  else
311  {
312  LOG(VB_EIT, LOG_DEBUG, LOC +
313  QString("Updated %1 modified entries of %2 for chanid %3 in cache.")
314  .arg(updated).arg(size).arg(chanid));
315  }
316  }
317  if (removed)
318  {
319  LOG(VB_EIT, LOG_DEBUG, LOC + QString("Removed %1 old entries of %2 "
320  "for chanid %3 from cache.")
321  .arg(removed).arg(size).arg(chanid));
322  }
323  m_pruneCnt += removed;
324 
325  return true;
326 }
327 
329 {
330  QMutexLocker locker(&m_eventMapLock);
331 
332  QStringList value_clauses;
333  key_map_t::iterator it = m_channelMap.begin();
334  while (it != m_channelMap.end())
335  {
336  if (!WriteChannelToDB(value_clauses, it.key()))
337  it = m_channelMap.erase(it);
338  else
339  ++it;
340  }
341 
342  if (m_persistent)
343  {
344  if(value_clauses.isEmpty())
345  {
346  return;
347  }
348 
349  MSqlQuery query(MSqlQuery::InitCon());
350  query.prepare(QString("REPLACE INTO eit_cache "
351  "(chanid, eventid, tableid, version, endtime) "
352  "VALUES %1").arg(value_clauses.join(",")));
353  if (!query.exec())
354  {
355  MythDB::DBError("Error updating eitcache", query);
356  }
357  }
358 }
359 
360 bool EITCache::IsNewEIT(uint chanid, uint tableid, uint version,
361  uint eventid, uint endtime)
362 {
363  m_accessCnt++;
364 
365  if (m_accessCnt % 10000 == 0)
366  {
367  LOG(VB_EIT, LOG_INFO, LOC + GetStatistics());
368  ResetStatistics();
369  }
370 
371  // Don't re-add pruned entries
372  if (endtime < m_lastPruneTime)
373  {
374  m_prunedHitCnt++;
375  return false;
376  }
377 
378  // Validity check, reject events with endtime over 7 weeks in the future
379  if (endtime > m_lastPruneTime + 50 * 86400)
380  {
381  m_futureHitCnt++;
382  return false;
383  }
384 
385  QMutexLocker locker(&m_eventMapLock);
386  if (!m_channelMap.contains(chanid))
387  {
388  m_channelMap[chanid] = LoadChannel(chanid);
389  }
390 
391  if (!m_channelMap[chanid])
392  {
394  return false;
395  }
396 
397  event_map_t * eventMap = m_channelMap[chanid];
398  event_map_t::iterator it = eventMap->find(eventid);
399  if (it != eventMap->end())
400  {
401  if (extract_table_id(*it) > tableid)
402  {
403  // EIT from lower (ie. better) table number
404  m_tblChgCnt++;
405  }
406  else if ((extract_table_id(*it) == tableid) &&
407  (extract_version(*it) != version))
408  {
409  // EIT updated version on current table
410  m_verChgCnt++;
411  }
412  else if (extract_endtime(*it) != endtime)
413  {
414  // Endtime (starttime + duration) changed
415  m_endChgCnt++;
416  }
417  else
418  {
419  // EIT data previously seen
420  m_hitCnt++;
421  return false;
422  }
423  }
424 
425  eventMap->insert(eventid, construct_sig(tableid, version, endtime, true));
426  m_entryCnt++;
427 
428  return true;
429 }
430 
436 {
437  if (VERBOSE_LEVEL_CHECK(VB_EIT, LOG_DEBUG))
438  {
439  QDateTime tmptime = MythDate::fromSecsSinceEpoch(timestamp);
440  LOG(VB_EIT, LOG_INFO,
441  LOC + "Pruning all entries that ended before UTC " +
442  tmptime.toString(Qt::ISODate));
443  }
444 
445  m_lastPruneTime = timestamp;
446 
447  // Write all modified entries to DB and start with a clean cache
448  WriteToDB();
449 
450  // Prune old entries in the DB
451  if (m_persistent)
452  {
453  delete_in_db(timestamp);
454  }
455 
456  return 0;
457 }
458 
459 
464 {
465  MSqlQuery query(MSqlQuery::InitCon());
466 
467  QString qstr =
468  "DELETE FROM eit_cache "
469  "WHERE status = :STATUS";
470 
471  query.prepare(qstr);
472  query.bindValue(":STATUS", CHANNEL_LOCK);
473 
474  if (!query.exec())
475  MythDB::DBError("Error clearing channel locks", query);
476 }
477 
478 /* vim: set expandtab tabstop=4 shiftwidth=4: */
replace_in_db
static void replace_in_db(QStringList &value_clauses, uint chanid, uint eventid, uint64_t sig)
Definition: eitcache.cpp:96
MSqlQuery::isActive
bool isActive(void) const
Definition: mythdbcon.h:215
MSqlQuery::next
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:813
MSqlQuery
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:127
EITCache::m_pruneCnt
uint m_pruneCnt
Definition: eitcache.h:57
extract_endtime
static uint extract_endtime(uint64_t sig)
Definition: eitcache.cpp:86
EITCache::ClearChannelLocks
static MTV_PUBLIC void ClearChannelLocks(void)
Removes old channel locks, use it only at master backend start.
Definition: eitcache.cpp:463
LOC
#define LOC
Definition: eitcache.cpp:17
extract_table_id
static uint extract_table_id(uint64_t sig)
Definition: eitcache.cpp:76
mythdb.h
EITCache::m_verChgCnt
uint m_verChgCnt
Definition: eitcache.h:54
lock_channel
static bool lock_channel(uint chanid, uint endtime)
Definition: eitcache.cpp:127
VERBOSE_LEVEL_CHECK
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
Definition: mythlogging.h:29
MSqlQuery::value
QVariant value(int i) const
Definition: mythdbcon.h:204
MSqlQuery::exec
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:619
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
EITCache::m_channelMap
key_map_t m_channelMap
Definition: eitcache.h:42
EITCache::m_wrongChannelHitCnt
uint m_wrongChannelHitCnt
Definition: eitcache.h:60
MythDate::current
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:14
EITCache::WriteToDB
void WriteToDB(void)
Definition: eitcache.cpp:328
EITCache::m_persistent
bool m_persistent
Definition: eitcache.h:48
EITCache::m_hitCnt
uint m_hitCnt
Definition: eitcache.h:52
EITCache::EITCache
EITCache()
Definition: eitcache.cpp:22
mythdate.h
MythDate::fromSecsSinceEpoch
MBASE_PUBLIC QDateTime fromSecsSinceEpoch(int64_t seconds)
This function takes the number of seconds since the start of the epoch and returns a QDateTime with t...
Definition: mythdate.cpp:72
EITCache::m_tblChgCnt
uint m_tblChgCnt
Definition: eitcache.h:53
mythlogging.h
unlock_channel
static void unlock_channel(uint chanid, uint updated)
Definition: eitcache.cpp:178
MSqlQuery::InitCon
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:551
eitcache.h
MythDB::DBError
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:226
STATISTIC
@ STATISTIC
Definition: eitcache.cpp:124
EITCache::GetStatistics
QString GetStatistics(void) const
Definition: eitcache.cpp:47
EITCache::PruneOldEntries
uint PruneOldEntries(uint utc_timestamp)
Prunes entries that describe events ending before timestamp time.
Definition: eitcache.cpp:435
uint
unsigned int uint
Definition: compat.h:81
construct_sig
static uint64_t construct_sig(uint tableid, uint version, uint endtime, bool modified)
Definition: eitcache.cpp:69
EITCache::ResetStatistics
void ResetStatistics(void)
Definition: eitcache.cpp:33
EITDATA
@ EITDATA
Definition: eitcache.cpp:122
EITCache::m_accessCnt
uint m_accessCnt
Definition: eitcache.h:51
EITCache::m_futureHitCnt
uint m_futureHitCnt
Definition: eitcache.h:59
EITCache::m_lastPruneTime
uint m_lastPruneTime
Definition: eitcache.h:45
EITCache::IsNewEIT
bool IsNewEIT(uint chanid, uint tableid, uint version, uint eventid, uint endtime)
Definition: eitcache.cpp:360
EITCache::m_eventMapLock
QMutex m_eventMapLock
Definition: eitcache.h:44
extract_version
static uint extract_version(uint64_t sig)
Definition: eitcache.cpp:81
EITCache::WriteChannelToDB
bool WriteChannelToDB(QStringList &value_clauses, uint chanid)
Definition: eitcache.cpp:261
EITCache::kVersionMax
static const uint kVersionMax
Definition: eitcache.h:62
CHANNEL_LOCK
@ CHANNEL_LOCK
Definition: eitcache.cpp:123
EITCache::m_entryCnt
uint m_entryCnt
Definition: eitcache.h:56
event_map_t
QMap< uint, uint64_t > event_map_t
Definition: eitcache.h:19
MSqlQuery::bindValue
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:889
MythDate::ISODate
@ ISODate
Default UTC.
Definition: mythdate.h:17
EITCache::~EITCache
~EITCache()
Definition: eitcache.cpp:28
mythcontext.h
channel_status
channel_status
Definition: eitcache.cpp:120
EITCache::m_endChgCnt
uint m_endChgCnt
Definition: eitcache.h:55
EITCache::LoadChannel
event_map_t * LoadChannel(uint chanid)
Definition: eitcache.cpp:210
modified
static bool modified(uint64_t sig)
Definition: eitcache.cpp:91
delete_in_db
static void delete_in_db(uint endtime)
Definition: eitcache.cpp:104
nv_python_libs.bbciplayer.bbciplayer_api.version
string version
Definition: bbciplayer_api.py:77
EITCache::m_prunedHitCnt
uint m_prunedHitCnt
Definition: eitcache.h:58
MSqlQuery::prepare
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:838