/usr/lib/imh-ultrastack-common/venv/lib/python3.13/site-packages/ngxutil
NameSizeModeActions
__pycache__/-0755rm
api.py47230644editdlrm
cache.py69380644editdlrm
cli.py107020644editdlrm
info.py149160644editdlrm
logparse.py105370644editdlrm
profile.py17580644editdlrm
report.py146180644editdlrm
sendmail.py13740644editdlrm
util.py28880644editdlrm
vts.py17100644editdlrm
__init__.py4050644editdlrm
Edit: /usr/lib/imh-ultrastack-common/venv/lib/python3.13/site-packages/ngxutil/report.py (14618B)
# vim: set ts=4 sw=4 expandtab syntax=python: """ ngxutil.report ANSI & HTML report generator @author J. Hipps """ import os import sys import logging import re from glob import glob import arrow from prettytable import PrettyTable from ngxutil import default_cache_base from ngxutil import logparse from ngxutil.vts import parse_vts from ngxutil.cache import read_cache_file from ngxutil.util import * # HTML table attribs tab_attribs = {'style': "border: 3px solid black; border-collapse: collapse; width: 650px; " "font-size: 16px; font-family: Helvetica,sans-serif; cell-padding: 10px"} logger = logging.getLogger('ngxutil') def repgen_header(vdat): """ Generate server status header Returns: (plain_text, html) """ # calc uptime try: uptime = format_uptime((vdat.get('nowMsec') - vdat.get('loadMsec')) / 1000.0) except: uptime = "???" pt = PrettyTable() pt.field_names = ('Server', 'Version', 'Uptime') try: pt.add_row((vdat.get('hostName'), vdat.get('nginxVersion'), uptime)) except: pass return (pt.get_string(), pt.get_html_string() + "
") def repgen_server(span=None, tail=None, infile='/var/log/nginx/access.log'): """ Generate a server-wide report """ vdata = parse_vts() ro, rh = repgen_header(vdata) rdom = repgen_domain('*', vdata, noheader=True, span=span, tail=tail, infile=infile) if rdom is None: return None ro += "\n\n" + rdom[0] + "\n\n" rh += "\n" + rdom[1] + "\n" return (ro, rh) def repgen_domain(domain, vdata=None, noheader=False, span=None, tail=None, infile='/var/log/nginx/access.log'): """ Generate a report for @domain """ if vdata is None: vdata = parse_vts() ro = ""; rh = "" duser = get_domain_owner(domain) if not duser: return None try: tvd = vdata['serverZones'][duser['vhost']] except: logger.error("Failed to retrieve status data for domain %s. Data not yet available.", domain) return None # determine loglines to read if tail is not None: llines = tail else: if span: llines = None else: llines = vdata['connections']['requests'] # Get access.log data, then filter it based on the vhost field try: logger.info("Parsing log '%s' (%s)...", infile, format_size(os.stat(infile).st_size)) except Exception as e: logger.error("Unable to stat input file: %s", str(e)) sys.exit(100) aclog = logparse.parse_log(infile, span=span, lastlines=llines) logdata = logparse.filter_log(aclog, 'vhost', duser['vhost'].lower()) xpc = logparse.get_cache_hitrate(logdata) # Domain header table if span: rqtot = len(logdata) rtimes = logparse.get_reqtime(logdata) rx_fmt = "---" tx_fmt = format_size(logparse.get_bytes(logdata)) avgtime_fmt = "Min/Avg/Max" avgtime_str = "{min:01.03f} / {avg:01.03f} / {max:01.03f}".format(**rtimes) else: rqtot = tvd['requestCounter'] rx_fmt = format_size(tvd['inBytes']) tx_fmt = format_size(tvd['outBytes']) avgtime_fmt = "Avg" avgtime_str = "{:01.03f}".format(float(sum(tvd['requestMsecs']['msecs'])) / \ float(len(tvd['requestMsecs']['msecs'])) / 1000.0) pt = PrettyTable() pt.field_names = ('RX bytes', 'TX bytes', 'Requests', 'Req Time (%s)' % (avgtime_fmt)) pt.add_row((rx_fmt, tx_fmt, rqtot, avgtime_str)) if span: rduration = format_uptime(int(span) * 3600) else: rduration = format_uptime((vdata.get('nowMsec') - vdata.get('loadMsec')) / 1000.0) if not noheader: ro = strcolor('white', "<< {} / {} >>\n".format(domain, duser['vhost'])) rh = "

" + domain + " / " + duser['vhost'] + "

\n" + \ gen_email_header(rduration) ro += pt.get_string() + "\n\n" rh += strip_colors(pt.get_html_string(attributes=tab_attribs)) + "
\n" # Add in cache stats cinfo = repgen_cache_info(duser['owner'], duser['vhost'], domain) ro += cinfo[0]; rh += cinfo[1] # Build response code table pt = PrettyTable() pt.field_names = ('1xx', '2xx', '3xx', '4xx', '5xx', 'TOTAL') if span: rpc = logparse.get_status_hitrate(logdata) pt.add_row((strcolor('magenta', mkpct(rpc.get('1xx', 0), rqtot)), strcolor('green', mkpct(rpc.get('2xx', 0), rqtot)), strcolor('blue', mkpct(rpc.get('3xx', 0), rqtot)), strcolor('yellow', mkpct(rpc.get('4xx', 0), rqtot)), strcolor('red', mkpct(rpc.get('5xx', 0), rqtot)), strcolor('white', mkpct(rqtot, rqtot)))) else: pt.add_row((strcolor('magenta', mkpct(tvd['responses']['1xx'], rqtot)), strcolor('green', mkpct(tvd['responses']['2xx'], rqtot)), strcolor('blue', mkpct(tvd['responses']['3xx'], rqtot)), strcolor('yellow', mkpct(tvd['responses']['4xx'], rqtot)), strcolor('red', mkpct(tvd['responses']['5xx'], rqtot)), strcolor('white', mkpct(rqtot, rqtot)))) ro += strcolor('white', "HTTP RESPONSE CODES\n") + pt.get_string() + "\n\n" rh += "

HTTP Response Codes

\n" + \ strip_colors(pt.get_html_string(attributes=tab_attribs)) + "
\n" # Build UCS tables hit_dtot = rqtot - xpc.get('STATIC/TYPE', 0) - xpc.get('STATIC/PATH', 0) - xpc.get('DISABLED', 0) - xpc.get('BYPASS', 0) hit_stot = rqtot - xpc.get('DISABLED', 0) - xpc.get('BYPASS', 0) - xpc.get('HIT', 0) hit_btot = rqtot - xpc.get('STATIC/TYPE', 0) - xpc.get('STATIC/PATH', 0) ptx = PrettyTable() ptx.field_names = ('HIT (Dynamic)', 'HIT (Static)', 'DISABLED+BYPASS') ptx.add_row((strcolor('green', mkpct(xpc.get('HIT', 0), hit_dtot)), strcolor('yellow', mkpct(xpc.get('STATIC/TYPE', 0) + xpc.get('STATIC/PATH', 0), hit_stot)), strcolor('red', mkpct(xpc.get('BYPASS', 0) + xpc.get('DISABLED', 0), hit_btot)))) pt = PrettyTable() pt.field_names = ('HIT', 'MISS', 'BYPASS', 'EXPIRED', 'UPDATING') pt.add_row((strcolor('green', mkpct(xpc.get('HIT', 0), rqtot)), strcolor('red', mkpct(xpc.get('MISS', 0), rqtot)), strcolor('magenta', mkpct(xpc.get('BYPASS', 0), rqtot)), strcolor('yellow', mkpct(xpc.get('EXPIRED', 0), rqtot)), strcolor('yellow', mkpct(xpc.get('UPDATING', 0), rqtot)))) pt2 = PrettyTable() pt2.field_names = ('STATIC/TYPE', 'STATIC/PATH', 'DISABLED', 'TOTAL') pt2.add_row((strcolor('green', mkpct(xpc.get('STATIC/TYPE', 0), rqtot)), strcolor('green', mkpct(xpc.get('STATIC/PATH', 0), rqtot)), strcolor('magenta', mkpct(xpc.get('DISABLED', 0), rqtot)), strcolor('white', mkpct(rqtot, rqtot)))) ro += strcolor('white', "CACHE STATUS\n") + ptx.get_string() + "\n" + \ pt.get_string() + "\n" + pt2.get_string() + "\n\n" rh += "

Cache Status

\n" + \ strip_colors(ptx.get_html_string(attributes=tab_attribs)) + "
\n" + \ strip_colors(pt.get_html_string(attributes=tab_attribs)) + "
\n" + \ strip_colors(pt2.get_html_string(attributes=tab_attribs)) + "
\n" # Show NLP datas fullurl = (domain == '*') cinfo = repgen_nlp(logdata, user=duser['owner'], fullurl=fullurl) ro += cinfo[0]; rh += cinfo[1] rh += gen_email_footer() return (ro, rh) def repgen_cache_info(zone, vhost=None, domain=None, cbase=default_cache_base): """ Return stats about currently cached pages """ gpath = os.path.join(cbase, zone, '*', '*') tally = {'files': 0, 'size': 0} for tfile in glob(gpath): cdata = read_cache_file(tfile) if not cdata: continue if domain: if not re.match(r'^https?(GET|HEAD|OPTIONS)(' + domain.lower() + r'|' + vhost.lower() + r')/?.*$', cdata['key'], re.I): continue tally['files'] += 1 tally['size'] += cdata['stat'].st_size # Build cache stats info pt = PrettyTable() pt.field_names = ('Total Files', 'Total Size') pt.add_row((strcolor('white', tally['files']), strcolor('white', format_size(tally['size'])))) ro = strcolor('white', "CACHE ZONE STATS\n") + pt.get_string() + "\n\n" rh = "

Cache Zone Status

\n" + \ strip_colors(pt.get_html_string(attributes=tab_attribs)) + "
\n" return (ro, rh) def repgen_nlp(logdata=None, user=None, lastlines=10000, topcount=10, rresolve=True, maxwidth=65, fullurl=False): """ Generate an ANSI and HTML report of top hits If @logdata is not supplied, then a fresh read of the log is done, pulling the last @lastlines lines from the file """ if not logdata: logdata = logparse.parse_log('/var/log/nginx/access.log', lastlines=lastlines) ro = ""; rh = "" # Top Hosts utop = list(logparse.get_top_hosts(logdata, topcount=topcount).values()) pt = PrettyTable() pt.add_column('HITS', [x['hits'] for x in utop]) pt.add_column('HOST', [x['host'] for x in utop]) pt.align['HITS'] = 'r' pt.align['HOST'] = 'l' ro += strcolor('white', "TOP HOSTS\n") + pt.get_string() + "\n\n" rh += "

Top Hosts

\n" + \ strip_colors(pt.get_html_string(attributes=tab_attribs)) + "
\n" # Top URIs utop = list(logparse.get_top_uris(logdata, czone=user, topcount=topcount).values()) pt = PrettyTable() pt.add_column('HITS', [x['hits'] for x in utop]) pt.add_column('METHOD', [x['method'] for x in utop]) pt.add_column('STATUS', [x['status'] for x in utop]) pt.add_column('CACHE', [x['cache'] for x in utop]) if fullurl: pt.add_column('HOST/URI', [(x['host'] + x['uri'])[:maxwidth] for x in utop]) pt.align['HOST/URI'] = 'l' else: pt.add_column('URI', [x['uri'][:maxwidth] for x in utop]) pt.align['URI'] = 'l' pt.align['HITS'] = 'r' ro += strcolor('white', "TOP URIs\n") + pt.get_string() + "\n\n" rh += "

Top URIs

\n" + \ strip_colors(pt.get_html_string(attributes=tab_attribs)) + "
\n" # Top User-Agents utop = logparse.get_top_agents(logdata, topcount=topcount) pt = PrettyTable() pt.add_column('COUNT', list(utop.values())) pt.add_column('USER-AGENT', [x[:maxwidth] for x in list(utop.keys())]) pt.align['COUNT'] = 'r' pt.align['USER-AGENT'] = 'l' ro += strcolor('white', "TOP USER-AGENTS\n") + pt.get_string() + "\n\n" rh += "

Top User-Agents

\n" + \ strip_colors(pt.get_html_string(attributes=tab_attribs)) + "
\n" # Top Locations if logdata and logdata[0].get('loc') is not None: utop = logparse.get_top_locations(logdata, topcount=topcount) pt = PrettyTable() pt.add_column('COUNT', list(utop.values())) pt.add_column('LOCATION', list(utop.keys())) pt.align['COUNT'] = 'r' pt.align['LOCATION'] = 'l' ro += strcolor('white', "TOP LOCATIONS\n") + pt.get_string() + "\n\n" rh += "

Top Locations

\n" + \ strip_colors(pt.get_html_string(attributes=tab_attribs)) + "
\n" # Top Client IPs utop = logparse.get_top_ips(logdata, rresolve=rresolve, topcount=topcount) pt = PrettyTable() pt.add_column('HITS', [x['hits'] for x in list(utop.values())]) pt.add_column('IP', list(utop.keys())) pt.add_column('HOST/PTR', [x['reverse'] for x in list(utop.values())]) pt.align['HITS'] = 'r' pt.align['HOST/PTR'] = 'l' ro += strcolor('white', "TOP CLIENT IPs\n") + pt.get_string() rh += "

Top Client IPs

\n" + \ strip_colors(pt.get_html_string(attributes=tab_attribs)) + "
\n" return (ro, rh) def gen_email_header(tperiod): """ Return email stats header """ outstr = """
Generated on %s Period: %s
""" % (arrow.now().format("YYYY-MM-DD HH:mm:ss ZZ"), tperiod) return outstr def gen_email_footer(): """ Return email stats footer """ outstr = """

Glossary

  • HIT - A valid (not expired) page was served from the cache
  • MISS - No valid cached page was available. A request was made to the application, and possibly cached for later use
  • BYPASS - The cache was bypassed due to meeting certain criteria (such as being logged in, using admin or user portal, using a checkout page in an ecommerce store, etc.)
  • EXPIRED - An expired page was served from the cache. This can happen if the origin server/application threw an error. Instead of showing the user an error page, an expired page from the cache is shown.
  • UPDATING - An expired page was served from the cache. Another request is in progress (by another client), which will update the cached page. This status typically only happens during very high traffic periods
  • STATIC/TYPE - Static content that was served directly by NGINX. The file extension (type) was used to serve the file directly from NGINX, rather than requesting the resource from Apache
  • STATIC/PATH - Static content that was served directly by NGINX. A specifically-defined path was used to make this determination
  • DISABLED - Caching has been explicitly disabled by the user
  • TOTAL - Total number of all requests. All constituent statuses may not add up to 100%, since pages without a specific cache status are not reported
  • """ return outstr