root/trunk/pdns/pdns/packetcache.cc @ 1304

Revision 1304, 9.5 KB (checked in by ahu, 5 years ago)

hook up packet cache statistics to the new boost::multi_index code

  • Property svn:eol-style set to native
  • Property svn:keywords set to author date id revision
Line 
1/*
2    PowerDNS Versatile Database Driven Nameserver
3    Copyright (C) 2002 - 2008  PowerDNS.COM BV
4
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License version 2 as
7    published by the Free Software Foundation
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software
16    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17*/
18#include "utility.hh"
19#include "packetcache.hh"
20#include "logger.hh"
21#include "arguments.hh"
22#include "statbag.hh"
23#include <map>
24#include <boost/algorithm/string.hpp>
25
26extern StatBag S;
27
28PacketCache::PacketCache()
29{
30  pthread_rwlock_init(&d_mut,0);
31  d_hit=d_miss=0;
32
33  d_ttl=-1;
34  d_recursivettl=-1;
35
36  S.declare("packetcache-hit");
37  S.declare("packetcache-miss");
38  S.declare("packetcache-size");
39
40  d_statnumhit=S.getPointer("packetcache-hit");
41  d_statnummiss=S.getPointer("packetcache-miss");
42  d_statnumentries=S.getPointer("packetcache-size");
43}
44
45int PacketCache::get(DNSPacket *p, DNSPacket *cached)
46{
47  extern StatBag S;
48  if(!((d_hit+d_miss)%150000)) {
49    cleanup();
50  }
51
52  if(d_ttl<0) 
53    getTTLS();
54
55  if(d_doRecursion && p->d.rd) { // wants recursion
56    if(!d_recursivettl) {
57      (*d_statnummiss)++;
58      d_miss++;
59      return 0;
60    }
61  }
62  else { // does not
63    if(!d_ttl) {
64      (*d_statnummiss)++;
65      d_miss++;
66      return 0;
67    }
68  }
69   
70  bool packetMeritsRecursion=d_doRecursion && p->d.rd;
71  if(ntohs(p->d.qdcount)!=1) // we get confused by packets with more than one question
72    return 0;
73
74  {
75    TryReadLock l(&d_mut); // take a readlock here
76    if(!l.gotIt()) {
77      S.inc("deferred-cache-lookup");
78      return 0;
79    }
80
81    if(!((d_hit+d_miss)%30000)) {
82      *d_statnumentries=d_map.size(); // needs lock
83    }
84    string value;
85
86    if(getEntry(p->qdomain, p->qtype, PacketCache::PACKETCACHE, value, -1, packetMeritsRecursion)) {
87      //      cerr<<"Packet cache hit for '"<<p->qdomain<<"', merits: "<<packetMeritsRecursion<<endl;
88      (*d_statnumhit)++;
89      d_hit++;
90      if(cached->parse(value.c_str(), value.size()) < 0) {
91        return -1;
92      }
93      cached->spoofQuestion(p->qdomain); // for correct case
94      return 1;
95    }
96  }
97  //  cerr<<"Packet cache miss for '"<<p->qdomain<<"', merits: "<<packetMeritsRecursion<<endl;
98  (*d_statnummiss)++;
99  d_miss++;
100  return 0; // bummer
101}
102
103void PacketCache::getTTLS()
104{
105  d_ttl=::arg().asNum("cache-ttl");
106  d_recursivettl=::arg().asNum("recursive-cache-ttl");
107
108  d_doRecursion=::arg().mustDo("recursor"); 
109}
110
111
112void PacketCache::insert(DNSPacket *q, DNSPacket *r)
113{
114  if(d_ttl < 0)
115    getTTLS();
116 
117  if(ntohs(q->d.qdcount)!=1) {
118    return; // do not try to cache packets with multiple questions
119  }
120
121  bool packetMeritsRecursion=d_doRecursion && q->d.rd;
122
123  insert(q->qdomain, q->qtype, PacketCache::PACKETCACHE, r->getString(), packetMeritsRecursion ? d_recursivettl : d_ttl, -1, packetMeritsRecursion);
124}
125
126// universal key appears to be: qname, qtype, kind (packet, query cache), optionally zoneid, meritsRecursion
127void PacketCache::insert(const string &qname, const QType& qtype, CacheEntryType cet, const string& value, unsigned int ttl, int zoneID, bool meritsRecursion)
128{
129  if(!ttl)
130    return;
131 
132  //  cerr<<"Inserting qname '"<<qname<<"', cet: "<<(int)cet<<", value: '"<< (cet ? value : "PACKET") <<"', qtype: "<<qtype.getName()<<", ttl: "<<ttl<<endl;
133  CacheEntry val;
134  val.ttd=time(0)+ttl;
135  val.qname=qname;
136  val.qtype=qtype.getCode();
137  val.value=value;
138  val.ctype=cet;
139  val.meritsRecursion=meritsRecursion;
140
141  TryWriteLock l(&d_mut);
142  if(l.gotIt()) { 
143    bool success;
144    cmap_t::iterator place;
145    tie(place, success)=d_map.insert(val);
146    //    cerr<<"Insert succeeded: "<<success<<endl;
147    if(!success)
148      d_map.replace(place, val);
149   
150  }
151  else 
152    S.inc("deferred-cache-inserts"); 
153}
154
155/** purges entries from the packetcache. If match ends on a $, it is treated as a suffix */
156int PacketCache::purge(const vector<string> &matches)
157{
158  WriteLock l(&d_mut);
159  int delcount=0;
160 
161  if(matches.empty()) {
162    delcount = d_map.size();
163    d_map.clear();
164    *d_statnumentries=0;
165    return delcount;
166  }
167
168  /* ok, the suffix delete plan. We want to be able to delete everything that
169     pertains 'www.powerdns.com' but we also want to be able to delete everything
170     in the powerdns.com zone, so: 'powerdns.com' and '*.powerdns.com'.
171
172     However, we do NOT want to delete 'usepowerdns.com!, nor 'powerdnsiscool.com'
173
174     So, at first shot, store in reverse label order:
175
176     'be.someotherdomain'
177     'com.powerdns'
178     'com.powerdns.images'
179     'com.powerdns.www'
180     'com.powerdnsiscool'
181     'com.usepowerdns.www'
182
183     If we get a request to remove 'everything above powerdns.com', we do a search for 'com.powerdns' which is guaranteed to come first (it is shortest!)
184     Then we delete everything that is either equal to 'com.powerdns' or begins with 'com.powerdns.' This trailing dot saves us
185     from deleting 'com.powerdnsiscool'.
186
187     We can stop the process once we reach something that doesn't match.
188
189     Ok - fine so far, except it doesn't work! Let's say there *is* no 'com.powerdns' in cache!
190
191     In that case our request doesn't find anything.. now what.
192     lower_bound to the rescue! It finds the place where 'com.powerdns' *would* be.
193     
194     Ok - next step, can we get away with simply reversing the string?
195
196     'moc.sndrewop'
197     'moc.sndrewop.segami'
198     'moc.sndrewop.www'
199     'moc.loocsidnsrewop'
200     'moc.dnsrewopesu.www'
201
202     Ok - next step, can we get away with only reversing the comparison?
203
204     'powerdns.com'
205     'images.powerdns.com'
206     '   www.powerdns.com'
207     'powerdnsiscool.com'
208     'www.userpowerdns.com'
209
210  */
211  for(vector<string>::const_iterator match = ++matches.begin(); match != matches.end() ; ++match) {
212    if(ends_with(*match, "$")) {
213      string suffix(*match);
214      suffix.resize(suffix.size()-1);
215
216      //    cerr<<"Begin dump!"<<endl;
217      cmap_t::const_iterator iter = d_map.lower_bound(tie(suffix));
218      cmap_t::const_iterator start=iter;
219      string dotsuffix = "."+suffix;
220
221      for(; iter != d_map.end(); ++iter) {
222        if(!iequals(iter->qname, suffix) && !iends_with(iter->qname, dotsuffix)) {
223          //    cerr<<"Stopping!"<<endl;
224          break;
225        }
226        //      cerr<<"Will erase '"<<iter->qname<<"'\n";
227
228        delcount++;
229      }
230      //    cerr<<"End dump!"<<endl;
231      d_map.erase(start, iter);
232    }
233    else {
234      delcount=d_map.count(tie(*match));
235      pair<cmap_t::iterator, cmap_t::iterator> range = d_map.equal_range(tie(*match));
236      d_map.erase(range.first, range.second);
237    }
238  }
239  *d_statnumentries=d_map.size();
240  return delcount;
241}
242
243bool PacketCache::getEntry(const string &qname, const QType& qtype, CacheEntryType cet, string& value, int zoneID, bool meritsRecursion)
244{
245  TryReadLock l(&d_mut); // take a readlock here
246  if(!l.gotIt()) {
247    S.inc( "deferred-cache-lookup");
248    return false;
249  }
250
251  uint16_t qt = qtype.getCode();
252  cmap_t::const_iterator i=d_map.find(tie(qname, qt, cet, zoneID, meritsRecursion));
253  time_t now=time(0);
254  bool ret=(i!=d_map.end() && i->ttd > now);
255  if(ret)
256    value = i->value;
257 
258  //  cerr<<"Cache hit: "<<(int)cet<<", "<<ret<<endl;
259 
260  return ret;
261}
262
263map<char,int> PacketCache::getCounts()
264{
265  ReadLock l(&d_mut);
266
267  map<char,int>ret;
268  int recursivePackets=0, nonRecursivePackets=0, queryCacheEntries=0, negQueryCacheEntries=0;
269
270  enum CacheEntryType { PACKETCACHE, QUERYCACHE, NEGCACHE};
271  for(cmap_t::const_iterator iter = d_map.begin() ; iter != d_map.end(); ++iter) {
272    if(iter->ctype == PACKETCACHE)
273      if(iter->meritsRecursion)
274        recursivePackets++;
275      else
276        nonRecursivePackets++;
277    else if(iter->ctype == QUERYCACHE)
278      queryCacheEntries++;
279    else if(iter->ctype == NEGCACHE)
280      negQueryCacheEntries++;
281  }
282
283  ret['!']=negQueryCacheEntries;
284  ret['Q']=queryCacheEntries;
285  ret['n']=nonRecursivePackets;
286  ret['r']=recursivePackets;
287  return ret;
288}
289
290int PacketCache::size()
291{
292  ReadLock l(&d_mut);
293  return d_map.size();
294}
295
296/** readlock for figuring out which iterators to delete, upgrade to writelock when actually cleaning */
297void PacketCache::cleanup()
298{
299  WriteLock l(&d_mut);
300
301  *d_statnumentries=d_map.size();
302
303  unsigned int maxCached=::arg().asNum("max-cache-entries");
304  unsigned int toTrim=0;
305 
306  unsigned int cacheSize=*d_statnumentries;
307
308  if(maxCached && cacheSize > maxCached) {
309    toTrim = cacheSize - maxCached;
310  }
311
312  unsigned int lookAt=0;
313  // two modes - if toTrim is 0, just look through 10000 records and nuke everything that is expired
314  // otherwise, scan first 5*toTrim records, and stop once we've nuked enough
315  if(toTrim)
316    lookAt=5*toTrim;
317  else
318    lookAt=cacheSize/10;
319
320  //  cerr<<"cacheSize: "<<cacheSize<<", lookAt: "<<lookAt<<", toTrim: "<<toTrim<<endl;
321  time_t now=time(0);
322
323  DLOG(L<<"Starting cache clean"<<endl);
324  if(d_map.empty())
325    return; // clean
326
327  typedef cmap_t::nth_index<1>::type sequence_t;
328  sequence_t& sidx=d_map.get<1>();
329  unsigned int erased=0;
330  for(sequence_t::iterator i=sidx.begin(); i != sidx.end();) {
331    if(i->ttd < now) {
332      sidx.erase(i++);
333      erased++;
334    }
335    else
336      ++i;
337
338    if(toTrim && erased > toTrim)
339      break;
340
341  }
342  //  cerr<<"erased: "<<erased<<endl;
343  *d_statnumentries=d_map.size();
344  DLOG(L<<"Done with cache clean"<<endl);
345}
Note: See TracBrowser for help on using the browser.