Skip to content

Commit 47b7b84

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Improve API log parsing"
2 parents 4fd2831 + 64d6867 commit 47b7b84

2 files changed

Lines changed: 58 additions & 21 deletions

File tree

lib/tls

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,7 @@ $listen_string
557557
ErrorLog $APACHE_LOG_DIR/tls-proxy_error.log
558558
ErrorLogFormat "%{cu}t [%-m:%l] [pid %P:tid %T] %7F: %E: [client\ %a] [frontend\ %A] %M% ,\ referer\ %{Referer}i"
559559
LogLevel info
560-
CustomLog $APACHE_LOG_DIR/tls-proxy_access.log "%{%Y-%m-%d}t %{%T}t.%{msec_frac}t [%l] %a \"%r\" %>s %b"
560+
CustomLog $APACHE_LOG_DIR/tls-proxy_access.log combined
561561
</VirtualHost>
562562
EOF
563563
if is_suse ; then

tools/get-stats.py

Lines changed: 57 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
#!/usr/bin/python3
22

33
import argparse
4+
import csv
45
import datetime
56
import glob
67
import itertools
78
import json
9+
import logging
810
import os
911
import re
1012
import socket
@@ -25,6 +27,8 @@
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

96100
def 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

Comments
 (0)