11#!/usr/bin/python3
22
33import argparse
4+ import csv
45import datetime
56import glob
67import itertools
78import json
9+ import logging
810import os
911import re
1012import socket
2527 print ('No pymysql, database information will not be included' ,
2628 file = sys .stderr )
2729
30+ LOG = logging .getLogger ('perf' )
31+
2832# https://www.elastic.co/blog/found-crash-elasticsearch#mapping-explosion
2933
3034
@@ -95,26 +99,56 @@ def get_db_stats(host, user, passwd):
9599
96100def get_http_stats_for_log (logfile ):
97101 stats = {}
98- for line in open (logfile ).readlines ():
99- m = re .search ('"([A-Z]+) /([^" ]+)( HTTP/1.1)?" ([0-9]{3}) ([0-9]+)' ,
100- line )
101- if m :
102- method = m .group (1 )
103- path = m .group (2 )
104- status = m .group (4 )
105- size = int (m .group (5 ))
106-
107- try :
108- service , rest = path .split ('/' , 1 )
109- except ValueError :
110- # Root calls like "GET /identity"
111- service = path
112- rest = ''
113-
114- stats .setdefault (service , {'largest' : 0 })
115- stats [service ].setdefault (method , 0 )
116- stats [service ][method ] += 1
117- stats [service ]['largest' ] = max (stats [service ]['largest' ], size )
102+ apache_fields = ('host' , 'a' , 'b' , 'date' , 'tz' , 'request' , 'status' ,
103+ 'length' , 'c' , 'agent' )
104+ ignore_agents = ('curl' , 'uwsgi' , 'nova-status' )
105+ for line in csv .reader (open (logfile ), delimiter = ' ' ):
106+ fields = dict (zip (apache_fields , line ))
107+ if len (fields ) != len (apache_fields ):
108+ # Not a combined access log, so we can bail completely
109+ return []
110+ try :
111+ method , url , http = fields ['request' ].split (' ' )
112+ except ValueError :
113+ method = url = http = ''
114+ if 'HTTP' not in http :
115+ # Not a combined access log, so we can bail completely
116+ return []
117+
118+ # Tempest's User-Agent is unchanged, but client libraries and
119+ # inter-service API calls use proper strings. So assume
120+ # 'python-urllib' is tempest so we can tell it apart.
121+ if 'python-urllib' in fields ['agent' ].lower ():
122+ agent = 'tempest'
123+ else :
124+ agent = fields ['agent' ].split (' ' )[0 ]
125+ if agent .startswith ('python-' ):
126+ agent = agent .replace ('python-' , '' )
127+ if '/' in agent :
128+ agent = agent .split ('/' )[0 ]
129+
130+ if agent in ignore_agents :
131+ continue
132+
133+ try :
134+ service , rest = url .strip ('/' ).split ('/' , 1 )
135+ except ValueError :
136+ # Root calls like "GET /identity"
137+ service = url .strip ('/' )
138+ rest = ''
139+
140+ method_key = '%s-%s' % (agent , method )
141+ try :
142+ length = int (fields ['length' ])
143+ except ValueError :
144+ LOG .warning ('[%s] Failed to parse length %r from line %r' % (
145+ logfile , fields ['length' ], line ))
146+ length = 0
147+ stats .setdefault (service , {'largest' : 0 })
148+ stats [service ].setdefault (method_key , 0 )
149+ stats [service ][method_key ] += 1
150+ stats [service ]['largest' ] = max (stats [service ]['largest' ],
151+ length )
118152
119153 # Flatten this for ES
120154 return [{'service' : service , 'log' : os .path .basename (logfile ),
@@ -131,6 +165,7 @@ def get_report_info():
131165 return {
132166 'timestamp' : datetime .datetime .now ().isoformat (),
133167 'hostname' : socket .gethostname (),
168+ 'version' : 2 ,
134169 }
135170
136171
@@ -152,6 +187,8 @@ def get_report_info():
152187 '(default is %s)' % ',' .join (process_defaults )))
153188 args = parser .parse_args ()
154189
190+ logging .basicConfig (level = logging .WARNING )
191+
155192 data = {
156193 'services' : get_services_stats (),
157194 'db' : pymysql and args .db_pass and get_db_stats (args .db_host ,
0 commit comments