Commit 6a4d6f5c authored by Vincent Pelletier's avatar Vincent Pelletier

Assorted preparatory work for a new feature

Switch APDEXStats class to use slots. This should save a bit of memory and
get a bit more speed.
Make word wrapping a bit more semantically sensible to make future diffs
more readable.
No functional change expected.
parent 3e0e69cd
#!/usr/bin/env python
##############################################################################
#
# Copyright (c) 2013 Nexedi SA and Contributors. All Rights Reserved.
# Copyright (c) 2013-2023 Nexedi SA and Contributors. All Rights Reserved.
# Vincent Pelletier <vincent@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
......@@ -252,6 +251,17 @@ def graph(title, data, options={}): # pylint: disable=dangerous-default-value
return ''.join(result)
class APDEXStats:
__slots__ = (
'threshold',
'threshold4',
'apdex_1',
'apdex_4',
'hit',
'duration_total',
'duration_max',
'getDuration',
)
def __init__(self, threshold, getDuration):
threshold *= US_PER_S
self.threshold = threshold
......@@ -318,32 +328,40 @@ class APDEXStats:
extra_right_class = 'overall_right'
else:
extra_right_class = ''
return '<td style="%(apdex_style)s" class="%(extra_class)s group_left">' \
'%(apdex)i%%</td><td class="%(extra_class)s">%(hit)s</td>' \
'<td class="%(average_class)s %(extra_class)s">%(average).2f</td>' \
'<td class="%(max_class)s %(extra_class)s group_right ' \
'%(extra_right_class)s">%(max).2f</td>' % {
'extra_class': extra_class,
'apdex_style': apdex_style,
'apdex': round(apdex * 100),
'hit': hit,
'average_class': getClassForDuration(average, threshold),
'average': average,
'max_class': getClassForDuration(maximum, threshold),
'max': maximum,
'extra_right_class': extra_right_class,
}
return (
f'<td style="{apdex_style}" class="{extra_class} group_left">{round(apdex * 100)}%</td>'
f'<td class="{extra_class}">{hit}</td>'
f'<td class="{getClassForDuration(average, threshold)} {extra_class}">{average:.2f}</td>'
f'<td class="{getClassForDuration(maximum, threshold)} {extra_class} group_right {extra_right_class}">{maximum:.2f}</td>'
)
_IGNORE_IN_STATE = (
'getDuration',
)
@classmethod
def fromJSONState(cls, state, getDuration):
result = cls(0, getDuration)
result.__dict__.update(state)
result = cls(
threshold=0,
getDuration=getDuration,
)
for key in self.__slots__:
if key in self._IGNORE_IN_STATE:
continue
try:
value = state[key]
except KeyError:
pass
else:
setattr(result, key, value)
return result
def asJSONState(self):
result = self.__dict__.copy()
del result['getDuration']
return result
return {
x: getattr(self, x)
for x in self.__slots__
if x not in self._IGNORE_IN_STATE
}
def _APDEXDateDictAsJSONState(date_dict):
return {
......@@ -352,10 +370,16 @@ def _APDEXDateDictAsJSONState(date_dict):
}
class GenericSiteStats:
def __init__(self, threshold, getDuration, suffix, error_detail=False,
user_agent_detail=False,
# Non-generic parameters
**_):
def __init__(
self,
threshold,
getDuration,
suffix,
error_detail=False,
user_agent_detail=False,
# Non-generic parameters
**_
):
self.threshold = threshold
self.suffix = suffix
self.error_detail = error_detail
......@@ -491,8 +515,13 @@ class GenericSiteStats:
@classmethod
def fromJSONState(cls, state, getDuration, suffix):
error_detail = state['error_detail']
result = cls(state['threshold'], getDuration, suffix, error_detail,
state.get('user_agent_detail', True))
result = cls(
threshold=state['threshold'],
getDuration=getDuration,
suffix=suffix,
error_detail=error_detail,
user_agent_detail=state.get('user_agent_detail', True),
)
if error_detail:
error_url_count = result.error_url_count
for state_status, state_url_dict in state['error_url_count'].items():
......@@ -550,10 +579,22 @@ class ERP5SiteStats(GenericSiteStats):
- If a line belongs to a module and has at least 2 slashes after module,
count line as belonging to a document of that module
"""
def __init__(self, threshold, getDuration, suffix, error_detail=False,
user_agent_detail=False, erp5_expand_other=False):
super().__init__(threshold, getDuration, suffix,
error_detail=error_detail, user_agent_detail=user_agent_detail)
def __init__(
self,
threshold,
getDuration,
suffix,
error_detail=False,
user_agent_detail=False,
erp5_expand_other=False,
):
super().__init__(
threshold,
getDuration,
suffix,
error_detail=error_detail,
user_agent_detail=user_agent_detail,
)
self.expand_other = erp5_expand_other
......@@ -1127,7 +1168,7 @@ def asHTML(
for caption, value in (
('apdex threshold', f'{args.apdex:.2f}s'),
('period', args.period or (period + ' (auto)')),
('timezone', args.to_timezone or "(input's)")
('timezone', args.to_timezone or "(input's)"),
):
out.write(f'<tr><th class="text">{caption}</th><td>{value}</td></tr>')
out.write(f'</table><h2>Hits per {period}</h2><table class="stats">'
......@@ -1615,9 +1656,13 @@ def main():
try:
site_data = per_site[site]
except KeyError:
site_data = per_site[site] = action(threshold, getDuration,
error_detail=error_detail, user_agent_detail=user_agent_detail,
erp5_expand_other=erp5_expand_other)
site_data = per_site[site] = action(
threshold=threshold,
getDuration=getDuration,
error_detail=error_detail,
user_agent_detail=user_agent_detail,
erp5_expand_other=erp5_expand_other,
)
try:
site_data.accumulate(match, url_match, hit_date)
except Exception: # pylint: disable=broad-exception-caught
......@@ -1658,16 +1703,5 @@ def main():
site_caption_dict,
)
if __name__ == '__main__':
__resource_base = os.path.join(*os.path.split(__file__)[:-1])
def getResource(name, encoding='utf-8'):
with open(
os.path.join(__resource_base, name),
encoding=encoding,
) as f:
return f.read()
main()
else:
def getResource(name, encoding='utf-8'):
return pkgutil.get_data(__name__, name).decode(encoding)
def getResource(name, encoding='utf-8'):
return pkgutil.get_data(__name__, name).decode(encoding)
##############################################################################
#
# Copyright (c) 2013-2023 Nexedi SA and Contributors. All Rights Reserved.
# Vincent Pelletier <vincent@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from . import main
main()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment