Coverage for drivers/lcache.py : 0%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#!/usr/bin/python3
2#
3# Copyright (C) Citrix Systems Inc.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published
7# by the Free Software Foundation; version 2.1 only.
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 Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program; if not, write to the Free Software Foundation, Inc.,
16# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18import os
19import blktap2
20import glob
21from stat import * # S_ISBLK(), ...
23SECTOR_SHIFT = 9
26class CachingTap(object):
28 def __init__(self, tapdisk, stats):
29 self.tapdisk = tapdisk
30 self.stats = stats
32 @classmethod
33 def from_tapdisk(cls, tapdisk, stats):
34 # pick the last image. if it's a VHD, we got a parent
35 # cache. the leaf case is an aio node sitting on a
36 # parent-caching tapdev. always checking the complementary
37 # case, so we bail on unexpected chains.
39 images = stats['images']
40 image = images[-1]
41 path = image['name']
42 _type = image['driver']['name']
44 def __assert(cond):
45 if not cond:
46 raise cls.NotACachingTapdisk(tapdisk, stats)
48 if _type == 'vhd':
49 # parent
51 return ParentCachingTap(tapdisk, stats)
53 elif _type == 'aio':
54 # leaf
55 st = os.stat(path)
57 __assert(S_ISBLK(st.st_mode))
59 major = os.major(st.st_rdev)
60 minor = os.minor(st.st_rdev)
62 __assert(major == tapdisk.major())
64 return LeafCachingTap(tapdisk, stats, minor)
66 __assert(0)
68 class NotACachingTapdisk(Exception):
70 def __init__(self, tapdisk, stats):
71 self.tapdisk = tapdisk
72 self.stats = stats
74 def __str__(self):
75 return \
76 "Tapdisk %s in state '%s' not found caching." % \
77 (self.tapdisk, self.stats)
80class ParentCachingTap(CachingTap):
82 def __init__(self, tapdisk, stats):
83 CachingTap.__init__(self, tapdisk, stats)
84 self.leaves = []
86 def add_leaves(self, tapdisks):
87 for t in tapdisks:
88 if t.is_child_of(self):
89 self.leaves.append(t)
91 def vdi_stats(self):
92 """Parent caching hits/miss count."""
94 images = self.stats['images']
95 total = self.stats['secs'][0]
97 rd_Gc = images[0]['hits'][0]
98 rd_lc = images[1]['hits'][0]
100 rd_hits = rd_Gc
101 rd_miss = total - rd_hits
103 return (rd_hits, rd_miss)
105 def vdi_stats_total(self):
106 """VDI total stats, including leaf hits/miss counts."""
108 rd_hits, rd_miss = self.vdi_stats()
109 wr_rdir = 0
111 for leaf in self.leaves:
112 l_rd_hits, l_rd_miss, l_wr_rdir = leaf.vdi_stats()
113 rd_hits += l_rd_hits
114 rd_miss += l_rd_miss
115 wr_rdir += l_wr_rdir
117 return rd_hits, rd_miss, wr_rdir
119 def __str__(self):
120 return "%s(%s, minor=%s)" % \
121 (self.__class__.__name__,
122 self.tapdisk.path, self.tapdisk.minor)
125class LeafCachingTap(CachingTap):
127 def __init__(self, tapdisk, stats, parent_minor):
128 CachingTap.__init__(self, tapdisk, stats)
129 self.parent_minor = parent_minor
131 def is_child_of(self, parent):
132 return parent.tapdisk.minor == self.parent_minor
134 def vdi_stats(self):
135 images = self.stats['images']
136 total = self.stats['secs'][0]
138 rd_Ac = images[0]['hits'][0]
139 rd_A = images[1]['hits'][0]
141 rd_hits = rd_Ac
142 rd_miss = rd_A
143 wr_rdir = self.stats['FIXME_enospc_redirect_count']
145 return rd_hits, rd_miss, wr_rdir
147 def __str__(self):
148 return "%s(%s, minor=%s)" % \
149 (self.__class__.__name__,
150 self.tapdisk.path, self.tapdisk.minor)
153class CacheFileSR(object):
155 CACHE_NODE_EXT = '.vhdcache'
157 def __init__(self, sr_path):
158 self.sr_path = sr_path
160 def is_mounted(self):
161 # NB. a basic check should do, currently only for CLI usage.
162 return os.path.exists(self.sr_path)
164 class NotAMountPoint(Exception):
166 def __init__(self, path):
167 self.path = path
169 def __str__(self):
170 return "Not a mount point: %s" % self.path
172 @classmethod
173 def from_uuid(cls, sr_uuid):
174 import SR
175 sr_path = "%s/%s" % (SR.MOUNT_BASE, sr_uuid)
177 cache_sr = cls(sr_path)
179 if not cache_sr.is_mounted():
180 raise cls.NotAMountPoint(sr_path)
182 return cache_sr
184 @classmethod
185 def from_session(cls, session):
186 import util
187 import SR as sm
189 host_ref = util.get_localhost_ref(session)
191 _host = session.xenapi.host
192 sr_ref = _host.get_local_cache_sr(host_ref)
193 if not sr_ref:
194 raise util.SMException("Local cache SR not specified")
196 if sr_ref == 'OpaqueRef:NULL':
197 raise util.SMException("Local caching not enabled.")
199 _SR = session.xenapi.SR
200 sr_uuid = _SR.get_uuid(sr_ref)
202 target = sm.SR.from_uuid(session, sr_uuid)
204 return cls(target.path)
206 @classmethod
207 def from_cli(cls):
208 import XenAPI # pylint: disable=import-error
210 session = XenAPI.xapi_local()
211 session.xenapi.login_with_password('root', '', '', 'SM')
213 return cls.from_session(session)
215 def statvfs(self):
216 return os.statvfs(self.sr_path)
218 def _fast_find_nodes(self):
219 pattern = "%s/*%s" % (self.sr_path, self.CACHE_NODE_EXT)
221 found = glob.glob(pattern)
223 return list(found)
225 def xapi_vfs_stats(self):
226 import util
228 f = self.statvfs()
229 if not f.f_frsize:
230 raise util.SMException("Cache FS does not report utilization.")
232 fs_size = f.f_frsize * f.f_blocks
233 fs_free = f.f_frsize * f.f_bfree
235 fs_cache_total = 0
236 for path in self._fast_find_nodes():
237 st = os.stat(path)
238 fs_cache_total += st.st_size
240 return {
241 'FREE_CACHE_SPACE_AVAILABLE':
242 fs_free,
243 'TOTAL_CACHE_UTILISATION':
244 fs_cache_total,
245 'TOTAL_UTILISATION_BY_NON_CACHE_DATA':
246 fs_size - fs_free - fs_cache_total
247 }
249 @classmethod
250 def _fast_find_tapdisks(cls):
251 import errno
252 # NB. we're only about to gather stats here, so take the
253 # fastpath, bypassing agent based VBD[currently-attached] ->
254 # VDI[allow-caching] -> Tap resolution altogether. Instead, we
255 # list all tapdisk and match by path suffix.
257 tapdisks = []
259 for tapdisk in blktap2.Tapdisk.list():
260 try:
261 ext = os.path.splitext(tapdisk.path)[1]
262 except:
263 continue
265 if ext != cls.CACHE_NODE_EXT:
266 continue
268 try:
269 stats = tapdisk.stats()
270 except blktap2.TapCtl.CommandFailure as e:
271 if e.errno != errno.ENOENT:
272 raise
273 continue # shut down
275 caching = CachingTap.from_tapdisk(tapdisk, stats)
276 tapdisks.append(caching)
278 return tapdisks
280 def fast_scan_topology(self):
281 # NB. gather all tapdisks. figure out which ones are leaves
282 # and which ones cache parents.
284 parents = []
285 leaves = []
287 for caching in self._fast_find_tapdisks():
288 if type(caching) == ParentCachingTap:
289 parents.append(caching)
290 else:
291 leaves.append(caching)
293 for parent in parents:
294 parent.add_leaves(leaves)
296 return parents
298 def vdi_stats_total(self):
300 parents = self.fast_scan_topology()
302 rd_hits, rd_miss, wr_rdir = 0, 0, 0
304 for parent in parents:
305 p_rd_hits, p_rd_miss, p_wr_rdir = parent.vdi_stats_total()
306 rd_hits += p_rd_hits
307 rd_miss += p_rd_miss
308 wr_rdir += p_wr_rdir
310 return rd_hits, rd_miss, wr_rdir
312 def xapi_vdi_stats(self):
313 rd_hits, rd_miss, wr_rdir = self.vdi_stats_total()
315 return {
316 'TOTAL_CACHE_HITS':
317 rd_hits << SECTOR_SHIFT,
318 'TOTAL_CACHE_MISSES':
319 rd_miss << SECTOR_SHIFT,
320 'TOTAL_CACHE_ENOSPACE_REDIRECTS':
321 wr_rdir << SECTOR_SHIFT,
322 }
324 def xapi_stats(self):
326 vfs = self.xapi_vfs_stats()
327 vdi = self.xapi_vdi_stats()
329 vfs.update(vdi)
330 return vfs
332CacheSR = CacheFileSR
334if __name__ == '__main__':
336 import sys
337 from pprint import pprint
339 args = list(sys.argv)
340 prog = args.pop(0)
341 prog = os.path.basename(prog)
344 def usage(stream):
345 if prog == 'tapdisk-cache-stats':
346 print("usage: tapdisk-cache-stats [<sr-uuid>]", file=stream)
347 else:
348 print("usage: %s sr.{stats|topology} [<sr-uuid>]" % prog, file=stream)
351 def usage_error():
352 usage(sys.stderr)
353 sys.exit(1)
355 if prog == 'tapdisk-cache-stats':
356 cmd = 'sr.stats'
357 else:
358 try:
359 cmd = args.pop(0)
360 except IndexError:
361 usage_error()
363 try:
364 _class, method = cmd.split('.')
365 except:
366 usage(sys.stderr)
367 sys.exit(1)
369 if _class == 'sr':
370 try:
371 uuid = args.pop(0)
372 except IndexError:
373 cache_sr = CacheSR.from_cli()
374 else:
375 cache_sr = CacheSR.from_uuid(uuid)
377 if method == 'stats':
379 d = cache_sr.xapi_stats()
380 for item in d.items():
381 print("%s=%s" % item)
383 elif method == 'topology':
384 parents = cache_sr.fast_scan_topology()
386 for parent in parents:
387 print(parent, "hits/miss=%s total=%s" % \
388 (parent.vdi_stats(), parent.vdi_stats_total()))
389 pprint(parent.stats)
391 for leaf in parent.leaves:
392 print(leaf, "hits/miss=%s" % str(leaf.vdi_stats()))
393 pprint(leaf.stats)
395 print("sr.total=%s" % str(cache_sr.vdi_stats_total()))
397 else:
398 usage_error()
399 else:
400 usage_error()