2 from datetime
import datetime
4 from urlparse
import urlparse
5 from optparse
import make_option
7 from couchdbkit
import Database
8 from restkit
import BasicAuth
9 from restkit
.errors
import RequestError
10 from django
.core
.management
.base
import BaseCommand
11 from django
.conf
import settings
13 from mygpo
.decorators
import repeat_on_conflict
14 from mygpo
.core
.models
import SanitizingRule
15 from mygpo
.utils
import progress
19 class Command(BaseCommand
):
21 Compacts the database and all views, and measures the required time
24 option_list
= BaseCommand
.option_list
+ (
25 make_option('--dry-run', action
='store_true', dest
='dryrun', default
=False, help="Don't compact anything."),
29 def handle(self
, *args
, **options
):
30 db_urls
= set(db
[1] for db
in settings
.COUCHDB_DATABASES
)
34 couchdb_admins
= getattr(settings
, 'COUCHDB_ADMINS', ())
36 username
, passwd
= couchdb_admins
[0]
37 filters
.append(BasicAuth(username
, passwd
))
39 for db_url
in db_urls
:
40 db
= Database(db_url
, filters
=filters
)
41 for view_hash
, name
, compact
, is_compacting
, get_size
in self
.get_compacters(db
):
43 if options
.get('dryrun'):
44 duration
, size_before
, size_after
= 0, 0, 0
46 duration
, size_before
, size_after
= self
.compact_wait(compact
, is_compacting
, get_size
)
48 print '%-40s %17s %10s %10s %7s' % (name
, duration
, self
.prettySize(size_before
), self
.prettySize(size_after
), view_hash
[:5])
51 def get_compacters(self
, db
):
52 """ Returns tuples containing compaction tasks """
54 compact_db
= lambda: db
.compact()
55 db_is_compacting
= lambda: db
.info()['compact_running']
56 get_db_size
= lambda: db
.info()['disk_size']
58 yield ('', db
.dbname
, compact_db
, db_is_compacting
, get_db_size
)
60 for view_hash
, design_doc
in self
.get_design_docs(db
):
61 compact_view
= lambda: db
.compact('%s' % design_doc
)
62 view_is_compacting
= lambda: db
.res
.get('/_design/%s/_info' % design_doc
).json_body
['view_index']['compact_running']
63 get_view_size
= lambda: db
.res
.get('/_design/%s/_info' % design_doc
).json_body
['view_index']['disk_size']
64 yield (view_hash
, design_doc
, compact_view
, view_is_compacting
, get_view_size
)
68 def get_all_design_docs(db
):
69 """ Returns all design documents in the database """
72 prefix_len
= len(prefix
)
73 return (ddoc
['key'][prefix_len
:] for ddoc
in db
.view('_all_docs', startkey
='_design/', endkey
='_design0'))
76 def get_design_docs(self
, db
):
78 Return one design doc for each index file
81 for ddoc
in self
.get_all_design_docs(db
):
82 sig
= db
.res
.get('/_design/%s/_info' % ddoc
).json_body
['view_index']['signature']
89 def compact_wait(compact
, is_compacting
, get_size
, sleep_time
=300, inc_factor
= 1):
90 """ Compacts the view and waits for the compaction to finish
92 Reports elapsed time and the view size, before and after the compaction """
94 start
= datetime
.utcnow()
95 size_before
= get_size()
101 except RequestError
as e
:
102 print >> sys
.stderr
, e
107 is_comp
= is_compacting()
109 size_before
= get_size()
111 sleep_time
*= inc_factor
115 except RequestError
as e
:
116 print >> sys
.stderr
, e
119 end
= datetime
.utcnow()
120 size_after
= get_size()
122 return end
- start
, size_before
, size_after
126 def prettySize(size
):
127 # http://snippets.dzone.com/posts/show/5434
128 suffixes
= [("B",2**10), ("K",2**20), ("M",2**30), ("G",2**40), ("T",2**50)]
129 for suf
, lim
in suffixes
:
133 return round(size
/float(lim
/2**10),2).__str
__()+suf