cpstats.py (13488B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\carbon\common\lib\cherrypy\lib\cpstats.py 2 import logging 3 if not hasattr(logging, 'statistics'): 4 logging.statistics = {} 5 6 def extrapolate_statistics(scope): 7 c = {} 8 for k, v in list(scope.items()): 9 if isinstance(v, dict): 10 v = extrapolate_statistics(v) 11 elif isinstance(v, (list, tuple)): 12 v = [ extrapolate_statistics(record) for record in v ] 13 elif hasattr(v, '__call__'): 14 v = v(scope) 15 c[k] = v 16 17 return c 18 19 20 import threading 21 import time 22 import cherrypy 23 appstats = logging.statistics.setdefault('CherryPy Applications', {}) 24 appstats.update({'Enabled': True, 25 'Bytes Read/Request': lambda s: s['Total Requests'] and s['Total Bytes Read'] / float(s['Total Requests']) or 0.0, 26 'Bytes Read/Second': lambda s: s['Total Bytes Read'] / s['Uptime'](s), 27 'Bytes Written/Request': lambda s: s['Total Requests'] and s['Total Bytes Written'] / float(s['Total Requests']) or 0.0, 28 'Bytes Written/Second': lambda s: s['Total Bytes Written'] / s['Uptime'](s), 29 'Current Time': lambda s: time.time(), 30 'Current Requests': 0, 31 'Requests/Second': lambda s: float(s['Total Requests']) / s['Uptime'](s), 32 'Server Version': cherrypy.__version__, 33 'Start Time': time.time(), 34 'Total Bytes Read': 0, 35 'Total Bytes Written': 0, 36 'Total Requests': 0, 37 'Total Time': 0, 38 'Uptime': lambda s: time.time() - s['Start Time'], 39 'Requests': {}}) 40 proc_time = lambda s: time.time() - s['Start Time'] 41 42 class ByteCountWrapper(object): 43 44 def __init__(self, rfile): 45 self.rfile = rfile 46 self.bytes_read = 0 47 48 def read(self, size = -1): 49 data = self.rfile.read(size) 50 self.bytes_read += len(data) 51 return data 52 53 def readline(self, size = -1): 54 data = self.rfile.readline(size) 55 self.bytes_read += len(data) 56 return data 57 58 def readlines(self, sizehint = 0): 59 total = 0 60 lines = [] 61 line = self.readline() 62 while line: 63 lines.append(line) 64 total += len(line) 65 if 0 < sizehint <= total: 66 break 67 line = self.readline() 68 69 return lines 70 71 def close(self): 72 self.rfile.close() 73 74 def __iter__(self): 75 return self 76 77 def next(self): 78 data = self.rfile.next() 79 self.bytes_read += len(data) 80 return data 81 82 83 average_uriset_time = lambda s: s['Count'] and s['Sum'] / s['Count'] or 0 84 85 class StatsTool(cherrypy.Tool): 86 87 def __init__(self): 88 cherrypy.Tool.__init__(self, 'on_end_request', self.record_stop) 89 90 def _setup(self): 91 if appstats.get('Enabled', False): 92 cherrypy.Tool._setup(self) 93 self.record_start() 94 95 def record_start(self): 96 request = cherrypy.serving.request 97 if not hasattr(request.rfile, 'bytes_read'): 98 request.rfile = ByteCountWrapper(request.rfile) 99 request.body.fp = request.rfile 100 r = request.remote 101 appstats['Current Requests'] += 1 102 appstats['Total Requests'] += 1 103 appstats['Requests'][threading._get_ident()] = {'Bytes Read': None, 104 'Bytes Written': None, 105 'Client': lambda s: '%s:%s' % (r.ip, r.port), 106 'End Time': None, 107 'Processing Time': proc_time, 108 'Request-Line': request.request_line, 109 'Response Status': None, 110 'Start Time': time.time()} 111 112 def record_stop(self, uriset = None, slow_queries = 1.0, slow_queries_count = 100, debug = False, **kwargs): 113 w = appstats['Requests'][threading._get_ident()] 114 r = cherrypy.request.rfile.bytes_read 115 w['Bytes Read'] = r 116 appstats['Total Bytes Read'] += r 117 if cherrypy.response.stream: 118 w['Bytes Written'] = 'chunked' 119 else: 120 cl = int(cherrypy.response.headers.get('Content-Length', 0)) 121 w['Bytes Written'] = cl 122 appstats['Total Bytes Written'] += cl 123 w['Response Status'] = cherrypy.response.status 124 w['End Time'] = time.time() 125 p = w['End Time'] - w['Start Time'] 126 w['Processing Time'] = p 127 appstats['Total Time'] += p 128 appstats['Current Requests'] -= 1 129 if debug: 130 cherrypy.log('Stats recorded: %s' % repr(w), 'TOOLS.CPSTATS') 131 if uriset: 132 rs = appstats.setdefault('URI Set Tracking', {}) 133 r = rs.setdefault(uriset, {'Min': None, 134 'Max': None, 135 'Count': 0, 136 'Sum': 0, 137 'Avg': average_uriset_time}) 138 if r['Min'] is None or p < r['Min']: 139 r['Min'] = p 140 if r['Max'] is None or p > r['Max']: 141 r['Max'] = p 142 r['Count'] += 1 143 r['Sum'] += p 144 if slow_queries and p > slow_queries: 145 sq = appstats.setdefault('Slow Queries', []) 146 sq.append(w.copy()) 147 if len(sq) > slow_queries_count: 148 sq.pop(0) 149 150 151 import cherrypy 152 cherrypy.tools.cpstats = StatsTool() 153 import os 154 thisdir = os.path.abspath(os.path.dirname(__file__)) 155 try: 156 import json 157 except ImportError: 158 try: 159 import simplejson as json 160 except ImportError: 161 json = None 162 163 missing = object() 164 locale_date = lambda v: time.strftime('%c', time.gmtime(v)) 165 iso_format = lambda v: time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(v)) 166 167 def pause_resume(ns): 168 169 def _pause_resume(enabled): 170 pause_disabled = '' 171 resume_disabled = '' 172 if enabled: 173 resume_disabled = 'disabled="disabled" ' 174 else: 175 pause_disabled = 'disabled="disabled" ' 176 return '\n <form action="pause" method="POST" style="display:inline">\n <input type="hidden" name="namespace" value="%s" />\n <input type="submit" value="Pause" %s/>\n </form>\n <form action="resume" method="POST" style="display:inline">\n <input type="hidden" name="namespace" value="%s" />\n <input type="submit" value="Resume" %s/>\n </form>\n ' % (ns, 177 pause_disabled, 178 ns, 179 resume_disabled) 180 181 return _pause_resume 182 183 184 class StatsPage(object): 185 formatting = {'CherryPy Applications': {'Enabled': pause_resume('CherryPy Applications'), 186 'Bytes Read/Request': '%.3f', 187 'Bytes Read/Second': '%.3f', 188 'Bytes Written/Request': '%.3f', 189 'Bytes Written/Second': '%.3f', 190 'Current Time': iso_format, 191 'Requests/Second': '%.3f', 192 'Start Time': iso_format, 193 'Total Time': '%.3f', 194 'Uptime': '%.3f', 195 'Slow Queries': {'End Time': None, 196 'Processing Time': '%.3f', 197 'Start Time': iso_format}, 198 'URI Set Tracking': {'Avg': '%.3f', 199 'Max': '%.3f', 200 'Min': '%.3f', 201 'Sum': '%.3f'}, 202 'Requests': {'Bytes Read': '%s', 203 'Bytes Written': '%s', 204 'End Time': None, 205 'Processing Time': '%.3f', 206 'Start Time': None}}, 207 'CherryPy WSGIServer': {'Enabled': pause_resume('CherryPy WSGIServer'), 208 'Connections/second': '%.3f', 209 'Start time': iso_format}} 210 211 def index(self): 212 yield '\n<html>\n<head>\n <title>Statistics</title>\n<style>\n\nth, td {\n padding: 0.25em 0.5em;\n border: 1px solid #666699;\n}\n\ntable {\n border-collapse: collapse;\n}\n\ntable.stats1 {\n width: 100%;\n}\n\ntable.stats1 th {\n font-weight: bold;\n text-align: right;\n background-color: #CCD5DD;\n}\n\ntable.stats2, h2 {\n margin-left: 50px;\n}\n\ntable.stats2 th {\n font-weight: bold;\n text-align: center;\n background-color: #CCD5DD;\n}\n\n</style>\n</head>\n<body>\n' 213 for title, scalars, collections in self.get_namespaces(): 214 yield "\n<h1>%s</h1>\n\n<table class='stats1'>\n <tbody>\n" % title 215 for i, (key, value) in enumerate(scalars): 216 colnum = i % 3 217 if colnum == 0: 218 yield '\n <tr>' 219 yield "\n <th>%(key)s</th><td id='%(title)s-%(key)s'>%(value)s</td>" % vars() 220 if colnum == 2: 221 yield '\n </tr>' 222 223 if colnum == 0: 224 yield '\n <th></th><td></td>\n <th></th><td></td>\n </tr>' 225 elif colnum == 1: 226 yield '\n <th></th><td></td>\n </tr>' 227 yield '\n </tbody>\n</table>' 228 for subtitle, headers, subrows in collections: 229 yield "\n<h2>%s</h2>\n<table class='stats2'>\n <thead>\n <tr>" % subtitle 230 for key in headers: 231 yield '\n <th>%s</th>' % key 232 233 yield '\n </tr>\n </thead>\n <tbody>' 234 for subrow in subrows: 235 yield '\n <tr>' 236 for value in subrow: 237 yield '\n <td>%s</td>' % value 238 239 yield '\n </tr>' 240 241 yield '\n </tbody>\n</table>' 242 243 yield '\n</body>\n</html>\n' 244 245 index.exposed = True 246 247 def get_namespaces(self): 248 s = extrapolate_statistics(logging.statistics) 249 for title, ns in sorted(s.items()): 250 scalars = [] 251 collections = [] 252 ns_fmt = self.formatting.get(title, {}) 253 for k, v in sorted(ns.items()): 254 fmt = ns_fmt.get(k, {}) 255 if isinstance(v, dict): 256 headers, subrows = self.get_dict_collection(v, fmt) 257 collections.append((k, ['ID'] + headers, subrows)) 258 elif isinstance(v, (list, tuple)): 259 headers, subrows = self.get_list_collection(v, fmt) 260 collections.append((k, headers, subrows)) 261 else: 262 format = ns_fmt.get(k, missing) 263 if format is None: 264 continue 265 if hasattr(format, '__call__'): 266 v = format(v) 267 elif format is not missing: 268 v = format % v 269 scalars.append((k, v)) 270 271 yield (title, scalars, collections) 272 273 def get_dict_collection(self, v, formatting): 274 headers = [] 275 for record in v.itervalues(): 276 for k3 in record: 277 format = formatting.get(k3, missing) 278 if format is None: 279 continue 280 if k3 not in headers: 281 headers.append(k3) 282 283 headers.sort() 284 subrows = [] 285 for k2, record in sorted(v.items()): 286 subrow = [k2] 287 for k3 in headers: 288 v3 = record.get(k3, '') 289 format = formatting.get(k3, missing) 290 if format is None: 291 continue 292 if hasattr(format, '__call__'): 293 v3 = format(v3) 294 elif format is not missing: 295 v3 = format % v3 296 subrow.append(v3) 297 298 subrows.append(subrow) 299 300 return (headers, subrows) 301 302 def get_list_collection(self, v, formatting): 303 headers = [] 304 for record in v: 305 for k3 in record: 306 format = formatting.get(k3, missing) 307 if format is None: 308 continue 309 if k3 not in headers: 310 headers.append(k3) 311 312 headers.sort() 313 subrows = [] 314 for record in v: 315 subrow = [] 316 for k3 in headers: 317 v3 = record.get(k3, '') 318 format = formatting.get(k3, missing) 319 if format is None: 320 continue 321 if hasattr(format, '__call__'): 322 v3 = format(v3) 323 elif format is not missing: 324 v3 = format % v3 325 subrow.append(v3) 326 327 subrows.append(subrow) 328 329 return (headers, subrows) 330 331 if json is not None: 332 333 def data(self): 334 s = extrapolate_statistics(logging.statistics) 335 cherrypy.response.headers['Content-Type'] = 'application/json' 336 return json.dumps(s, sort_keys=True, indent=4) 337 338 data.exposed = True 339 340 def pause(self, namespace): 341 logging.statistics.get(namespace, {})['Enabled'] = False 342 raise cherrypy.HTTPRedirect('./') 343 344 pause.exposed = True 345 pause.cp_config = {'tools.allow.on': True, 346 'tools.allow.methods': ['POST']} 347 348 def resume(self, namespace): 349 logging.statistics.get(namespace, {})['Enabled'] = True 350 raise cherrypy.HTTPRedirect('./') 351 352 resume.exposed = True 353 resume.cp_config = {'tools.allow.on': True, 354 'tools.allow.methods': ['POST']}