Ticket #12130: eitcache.cpp

File eitcache.cpp, 11.3 KB (added by klaas.de.waal@…, 12 years ago)

complete file including modifications

Line 
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 "eitcache.h"
11#include "mythcontext.h"
12#include "mythdb.h"
13#include "mythlogging.h"
14#include "mythdate.h"
15
16#define LOC QString("EITCache: ")
17
18// Highest version number. version is 5bits
19const uint EITCache::kVersionMax = 31;
20
21EITCache::EITCache()
22 : accessCnt(0), hitCnt(0), tblChgCnt(0), verChgCnt(0), endChgCnt(0),
23 entryCnt(0), pruneCnt(0), prunedHitCnt(0), futureHitCnt(0), wrongChannelHitCnt(0)
24{
25 // 24 hours ago
26 lastPruneTime = MythDate::current().toUTC().toTime_t() - 86400;
27}
28
29EITCache::~EITCache()
30{
31 WriteToDB();
32}
33
34void EITCache::ResetStatistics(void)
35{
36 accessCnt = 0;
37 hitCnt = 0;
38 tblChgCnt = 0;
39 verChgCnt = 0;
40 endChgCnt = 0;
41 entryCnt = 0;
42 pruneCnt = 0;
43 prunedHitCnt = 0;
44 futureHitCnt = 0;
45 wrongChannelHitCnt = 0;
46}
47
48QString EITCache::GetStatistics(void) const
49{
50 QMutexLocker locker(&eventMapLock);
51 return QString(
52 "EITCache::statistics: Accesses: %1, Hits: %2, "
53 "Table Upgrades %3, New Versions: %4, New Endtimes: %5, Entries: %6, "
54 "Pruned Entries: %7, Pruned Hits: %8, Future Hits: %9, Wrong Channel Hits %10, "
55 "Hit Ratio %11.")
56 .arg(accessCnt).arg(hitCnt).arg(tblChgCnt).arg(verChgCnt).arg(endChgCnt)
57 .arg(entryCnt).arg(pruneCnt).arg(prunedHitCnt).arg(futureHitCnt)
58 .arg(wrongChannelHitCnt)
59 .arg((hitCnt+prunedHitCnt+futureHitCnt+wrongChannelHitCnt)/(double)accessCnt);
60}
61
62static inline uint64_t construct_sig(uint tableid, uint version,
63 uint endtime, bool modified)
64{
65 return (((uint64_t) modified << 63) | ((uint64_t) tableid << 40) |
66 ((uint64_t) version << 32) | ((uint64_t) endtime));
67}
68
69static inline uint extract_table_id(uint64_t sig)
70{
71 return (sig >> 40) & 0xff;
72}
73
74static inline uint extract_version(uint64_t sig)
75{
76 return (sig >> 32) & 0x1f;
77}
78
79static inline uint extract_endtime(uint64_t sig)
80{
81 return sig & 0xffffffff;
82}
83
84static inline bool modified(uint64_t sig)
85{
86 return sig >> 63;
87}
88
89static void replace_in_db(uint chanid, uint eventid, uint64_t sig)
90{
91
92 MSqlQuery query(MSqlQuery::InitCon());
93
94 QString qstr =
95 "REPLACE INTO eit_cache "
96 " ( chanid, eventid, tableid, version, endtime) "
97 "VALUES (:CHANID, :EVENTID, :TABLEID, :VERSION, :ENDTIME)";
98
99 query.prepare(qstr);
100 query.bindValue(":CHANID", chanid);
101 query.bindValue(":EVENTID", eventid);
102 query.bindValue(":TABLEID", extract_table_id(sig));
103 query.bindValue(":VERSION", extract_version(sig));
104 query.bindValue(":ENDTIME", extract_endtime(sig));
105
106 if (!query.exec())
107 MythDB::DBError("Error updating eitcache", query);
108
109 return;
110}
111
112static void delete_in_db(uint endtime)
113{
114 LOG(VB_EIT, LOG_INFO, LOC + "Deleting old cache entries from the database");
115 MSqlQuery query(MSqlQuery::InitCon());
116
117 QString qstr =
118 "DELETE FROM eit_cache "
119 "WHERE endtime < :ENDTIME";
120
121 query.prepare(qstr);
122 query.bindValue(":ENDTIME", endtime);
123
124 if (!query.exec())
125 MythDB::DBError("Error deleting old eitcache entries.", query);
126
127 return;
128}
129
130
131#define EITDATA 0
132#define CHANNEL_LOCK 1
133#define STATISTIC 2
134
135static bool lock_channel(uint chanid, uint endtime)
136{
137 int lock = 1;
138 MSqlQuery query(MSqlQuery::InitCon());
139
140 QString qstr = "SELECT COUNT(*) "
141 "FROM eit_cache "
142 "WHERE chanid = :CHANID AND "
143 " endtime > :ENDTIME AND "
144 " status = :STATUS";
145
146 query.prepare(qstr);
147 query.bindValue(":CHANID", chanid);
148 query.bindValue(":ENDTIME", endtime);
149 query.bindValue(":STATUS", CHANNEL_LOCK);
150
151 if (!query.exec() || !query.isActive())
152 {
153 MythDB::DBError("Error checking for channel lock", query);
154 return false;
155 }
156
157 if (query.next())
158 lock = query.value(0).toInt();
159
160 if (lock)
161 {
162 LOG(VB_EIT, LOG_INFO,
163 LOC + QString("Ignoring channel %1 since it is locked.")
164 .arg(chanid));
165 return false;
166 }
167 else
168 {
169 uint now = MythDate::current().toTime_t();
170 qstr = "INSERT INTO eit_cache "
171 " ( chanid, endtime, status) "
172 "VALUES (:CHANID, :ENDTIME, :STATUS)";
173
174 query.prepare(qstr);
175 query.bindValue(":CHANID", chanid);
176 query.bindValue(":ENDTIME", now);
177 query.bindValue(":STATUS", CHANNEL_LOCK);
178
179 if (!query.exec())
180 {
181 MythDB::DBError("Error inserting channel lock", query);
182 return false;
183 }
184 }
185
186 return true;
187}
188
189static void unlock_channel(uint chanid, uint updated)
190{
191 MSqlQuery query(MSqlQuery::InitCon());
192
193 QString qstr =
194 "DELETE FROM eit_cache "
195 "WHERE chanid = :CHANID AND "
196 " status = :STATUS";
197
198 query.prepare(qstr);
199 query.bindValue(":CHANID", chanid);
200 query.bindValue(":STATUS", CHANNEL_LOCK);
201
202 if (!query.exec())
203 MythDB::DBError("Error deleting channel lock", query);
204
205 // inserting statistics
206 uint now = MythDate::current().toTime_t();
207 qstr = "REPLACE INTO eit_cache "
208 " ( chanid, eventid, endtime, status) "
209 "VALUES (:CHANID, :EVENTID, :ENDTIME, :STATUS)";
210
211 query.prepare(qstr);
212 query.bindValue(":CHANID", chanid);
213 query.bindValue(":EVENTID", updated);
214 query.bindValue(":ENDTIME", now);
215 query.bindValue(":STATUS", STATISTIC);
216
217 if (!query.exec())
218 MythDB::DBError("Error inserting eit statistics", query);
219}
220
221
222event_map_t * EITCache::LoadChannel(uint chanid)
223{
224 if (!lock_channel(chanid, lastPruneTime))
225 return NULL;
226
227 MSqlQuery query(MSqlQuery::InitCon());
228
229 QString qstr =
230 "SELECT eventid,tableid,version,endtime "
231 "FROM eit_cache "
232 "WHERE chanid = :CHANID AND "
233 " endtime > :ENDTIME AND "
234 " status = :STATUS";
235
236 query.prepare(qstr);
237 query.bindValue(":CHANID", chanid);
238 query.bindValue(":ENDTIME", lastPruneTime);
239 query.bindValue(":STATUS", EITDATA);
240
241 if (!query.exec() || !query.isActive())
242 {
243 MythDB::DBError("Error loading eitcache", query);
244 return NULL;
245 }
246
247 event_map_t * eventMap = new event_map_t();
248
249 while (query.next())
250 {
251 uint eventid = query.value(0).toUInt();
252 uint tableid = query.value(1).toUInt();
253 uint version = query.value(2).toUInt();
254 uint endtime = query.value(3).toUInt();
255
256 (*eventMap)[eventid] = construct_sig(tableid, version, endtime, false);
257 }
258
259 if (eventMap->size())
260 LOG(VB_EIT, LOG_INFO, LOC + QString("Loaded %1 entries for channel %2")
261 .arg(eventMap->size()).arg(chanid));
262
263 entryCnt += eventMap->size();
264 return eventMap;
265}
266
267void EITCache::WriteChannelToDB(uint chanid)
268{
269 event_map_t * eventMap = channelMap[chanid];
270
271 if (!eventMap)
272 {
273 channelMap.remove(chanid);
274 return;
275 }
276
277 uint size = eventMap->size();
278 uint updated = 0;
279 uint removed = 0;
280
281 event_map_t::iterator it = eventMap->begin();
282 while (it != eventMap->end())
283 {
284 if (extract_endtime(*it) > lastPruneTime)
285 {
286 if (modified(*it))
287 {
288 replace_in_db(chanid, it.key(), *it);
289 updated++;
290 *it &= ~(uint64_t)0 >> 1; // mark as synced
291 }
292 }
293 else
294 {
295 // Event is too old; remove from eit cache in memory
296 eventMap->remove(it.key());
297 removed++;
298 }
299 ++it;
300 }
301 unlock_channel(chanid, updated);
302
303 if (updated)
304 LOG(VB_EIT, LOG_INFO, LOC + QString("Wrote %1 modified entries of %2 "
305 "for channel %3 to database.")
306 .arg(updated).arg(size).arg(chanid));
307 if (removed)
308 LOG(VB_EIT, LOG_INFO, LOC + QString("Removed %1 old entries of %2 "
309 "for channel %3 from cache.")
310 .arg(removed).arg(size).arg(chanid));
311 pruneCnt += removed;
312}
313
314void EITCache::WriteToDB(void)
315{
316 QMutexLocker locker(&eventMapLock);
317
318 key_map_t::iterator it = channelMap.begin();
319 while (it != channelMap.end())
320 {
321 WriteChannelToDB(it.key());
322 ++it;
323 }
324}
325
326bool EITCache::IsNewEIT(uint chanid, uint tableid, uint version,
327 uint eventid, uint endtime)
328{
329 accessCnt++;
330
331 if (accessCnt % 500000 == 50000)
332 {
333 LOG(VB_EIT, LOG_INFO, GetStatistics());
334 WriteToDB();
335 }
336
337 // don't read pruned entries
338 if (endtime < lastPruneTime)
339 {
340 prunedHitCnt++;
341 return false;
342 }
343
344 // validity check, reject events with endtime over 7 weeks in the future
345 if (endtime > lastPruneTime + 50 * 86400)
346 {
347 futureHitCnt++;
348 return false;
349 }
350
351 QMutexLocker locker(&eventMapLock);
352 if (!channelMap.contains(chanid))
353 {
354 channelMap[chanid] = LoadChannel(chanid);
355 }
356
357 if (!channelMap[chanid])
358 {
359 wrongChannelHitCnt++;
360 return false;
361 }
362
363 event_map_t * eventMap = channelMap[chanid];
364 event_map_t::iterator it = eventMap->find(eventid);
365 if (it != eventMap->end())
366 {
367 if (extract_table_id(*it) > tableid)
368 {
369 // EIT from lower (ie. better) table number
370 tblChgCnt++;
371 }
372 else if ((extract_table_id(*it) == tableid) &&
373 ((extract_version(*it) < version) ||
374 ((extract_version(*it) == kVersionMax) &&
375 version < kVersionMax)))
376 {
377 // EIT updated version on current table
378 verChgCnt++;
379 }
380 else if (extract_endtime(*it) != endtime)
381 {
382 // Endtime (starttime + duration) changed
383 endChgCnt++;
384 }
385 else
386 {
387 // EIT data previously seen
388 hitCnt++;
389 return false;
390 }
391 }
392
393 eventMap->insert(eventid, construct_sig(tableid, version, endtime, true));
394 entryCnt++;
395
396 return true;
397}
398
399/** \fn EITCache::PruneOldEntries(uint timestamp)
400 * \brief Prunes entries that describe events ending before timestamp time.
401 * \return number of entries pruned
402 */
403uint EITCache::PruneOldEntries(uint timestamp)
404{
405 if (VERBOSE_LEVEL_CHECK(VB_EIT, LOG_INFO))
406 {
407 QDateTime tmptime = MythDate::fromTime_t(timestamp);
408 LOG(VB_EIT, LOG_INFO,
409 LOC + "Pruning all entries that ended before UTC " +
410 tmptime.toString(Qt::ISODate));
411 }
412
413 lastPruneTime = timestamp;
414
415 // Write all modified entries to DB and start with a clean cache
416 WriteToDB();
417
418 // Prune old entries in the DB
419 delete_in_db(timestamp);
420
421 return 0;
422}
423
424
425/** \fn EITCache::ClearChannelLocks(void)
426 * \brief removes old channel locks, use it only at master b<ackend start
427 */
428void EITCache::ClearChannelLocks(void)
429{
430 MSqlQuery query(MSqlQuery::InitCon());
431
432 QString qstr =
433 "DELETE FROM eit_cache "
434 "WHERE status = :STATUS";
435
436 query.prepare(qstr);
437 query.bindValue(":STATUS", CHANNEL_LOCK);
438
439 if (!query.exec())
440 MythDB::DBError("Error clearing channel locks", query);
441}
442
443/* vim: set expandtab tabstop=4 shiftwidth=4: */