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 <QChar> // Fix Qt6 GCC SFINAE warning
9#include <QDateTime>
10
12#include "libmythbase/mythdb.h"
14
15#include "eitcache.h"
16
17#define LOC QString("EITCache: ")
18
19// Highest version number. version is 5bits
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;
45}
46
47QString 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 */
69static 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
76static inline uint extract_table_id(uint64_t sig)
77{
78 return (sig >> 40) & 0xff;
79}
80
81static inline uint extract_version(uint64_t sig)
82{
83 return (sig >> 32) & 0x1f;
84}
85
86static inline uint extract_endtime(uint64_t sig)
87{
88 return sig & 0xffffffff;
89}
90
91static inline bool modified(uint64_t sig)
92{
93 return (sig >> 63) != 0U;
94}
95
96static 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
104static void delete_in_db(uint endtime)
105{
106 LOG(VB_EIT, LOG_INFO, LOC + "Deleting old cache entries from the database");
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
120enum channel_status : std::uint8_t
121{
124 STATISTIC = 2
126
127static bool lock_channel(uint chanid, uint endtime)
128{
129 int lock = 1;
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
178static void unlock_channel(uint chanid, uint updated)
179{
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
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
261bool 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 {
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
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
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());
369 }
370
371 // Don't re-add pruned entries
372 if (endtime < m_lastPruneTime)
373 {
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 {
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{
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: */
uint m_entryCnt
Definition: eitcache.h:56
~EITCache()
Definition: eitcache.cpp:28
uint PruneOldEntries(uint utc_timestamp)
Prunes entries that describe events ending before timestamp time.
Definition: eitcache.cpp:435
void ResetStatistics(void)
Definition: eitcache.cpp:33
EITCache()
Definition: eitcache.cpp:22
uint m_hitCnt
Definition: eitcache.h:52
uint m_verChgCnt
Definition: eitcache.h:54
static const uint kVersionMax
Definition: eitcache.h:62
uint m_wrongChannelHitCnt
Definition: eitcache.h:60
uint m_pruneCnt
Definition: eitcache.h:57
uint m_lastPruneTime
Definition: eitcache.h:45
void WriteToDB(void)
Definition: eitcache.cpp:328
uint m_endChgCnt
Definition: eitcache.h:55
bool WriteChannelToDB(QStringList &value_clauses, uint chanid)
Definition: eitcache.cpp:261
uint m_accessCnt
Definition: eitcache.h:51
uint m_prunedHitCnt
Definition: eitcache.h:58
bool IsNewEIT(uint chanid, uint tableid, uint version, uint eventid, uint endtime)
Definition: eitcache.cpp:360
static MTV_PUBLIC void ClearChannelLocks(void)
Removes old channel locks, use it only at master backend start.
Definition: eitcache.cpp:463
uint m_futureHitCnt
Definition: eitcache.h:59
bool m_persistent
Definition: eitcache.h:48
key_map_t m_channelMap
Definition: eitcache.h:42
QString GetStatistics(void) const
Definition: eitcache.cpp:47
event_map_t * LoadChannel(uint chanid)
Definition: eitcache.cpp:210
uint m_tblChgCnt
Definition: eitcache.h:53
QMutex m_eventMapLock
Definition: eitcache.h:44
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:129
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:839
QVariant value(int i) const
Definition: mythdbcon.h:205
bool isActive(void) const
Definition: mythdbcon.h:216
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:620
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:890
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:814
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:552
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:225
unsigned int uint
Definition: compat.h:60
static void delete_in_db(uint endtime)
Definition: eitcache.cpp:104
#define LOC
Definition: eitcache.cpp:17
static bool lock_channel(uint chanid, uint endtime)
Definition: eitcache.cpp:127
static bool modified(uint64_t sig)
Definition: eitcache.cpp:91
static uint extract_version(uint64_t sig)
Definition: eitcache.cpp:81
channel_status
Definition: eitcache.cpp:121
@ STATISTIC
Definition: eitcache.cpp:124
@ CHANNEL_LOCK
Definition: eitcache.cpp:123
@ EITDATA
Definition: eitcache.cpp:122
static uint extract_endtime(uint64_t sig)
Definition: eitcache.cpp:86
static void replace_in_db(QStringList &value_clauses, uint chanid, uint eventid, uint64_t sig)
Definition: eitcache.cpp:96
static void unlock_channel(uint chanid, uint updated)
Definition: eitcache.cpp:178
static uint64_t construct_sig(uint tableid, uint version, uint endtime, bool modified)
Definition: eitcache.cpp:69
static uint extract_table_id(uint64_t sig)
Definition: eitcache.cpp:76
QMap< uint, uint64_t > event_map_t
Definition: eitcache.h:19
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
Definition: mythlogging.h:29
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
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:81
@ ISODate
Default UTC.
Definition: mythdate.h:18
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:15
string version
Definition: giantbomb.py:185