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> # Vincent Pelletier <vincent@nexedi.com>
# #
# WARNING: This program as such is intended to be used by professional # 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 ...@@ -252,6 +251,17 @@ def graph(title, data, options={}): # pylint: disable=dangerous-default-value
return ''.join(result) return ''.join(result)
class APDEXStats: class APDEXStats:
__slots__ = (
'threshold',
'threshold4',
'apdex_1',
'apdex_4',
'hit',
'duration_total',
'duration_max',
'getDuration',
)
def __init__(self, threshold, getDuration): def __init__(self, threshold, getDuration):
threshold *= US_PER_S threshold *= US_PER_S
self.threshold = threshold self.threshold = threshold
...@@ -318,32 +328,40 @@ class APDEXStats: ...@@ -318,32 +328,40 @@ class APDEXStats:
extra_right_class = 'overall_right' extra_right_class = 'overall_right'
else: else:
extra_right_class = '' extra_right_class = ''
return '<td style="%(apdex_style)s" class="%(extra_class)s group_left">' \ return (
'%(apdex)i%%</td><td class="%(extra_class)s">%(hit)s</td>' \ f'<td style="{apdex_style}" class="{extra_class} group_left">{round(apdex * 100)}%</td>'
'<td class="%(average_class)s %(extra_class)s">%(average).2f</td>' \ f'<td class="{extra_class}">{hit}</td>'
'<td class="%(max_class)s %(extra_class)s group_right ' \ f'<td class="{getClassForDuration(average, threshold)} {extra_class}">{average:.2f}</td>'
'%(extra_right_class)s">%(max).2f</td>' % { f'<td class="{getClassForDuration(maximum, threshold)} {extra_class} group_right {extra_right_class}">{maximum:.2f}</td>'
'extra_class': extra_class, )
'apdex_style': apdex_style,
'apdex': round(apdex * 100), _IGNORE_IN_STATE = (
'hit': hit, 'getDuration',
'average_class': getClassForDuration(average, threshold), )
'average': average,
'max_class': getClassForDuration(maximum, threshold),
'max': maximum,
'extra_right_class': extra_right_class,
}
@classmethod @classmethod
def fromJSONState(cls, state, getDuration): def fromJSONState(cls, state, getDuration):
result = cls(0, getDuration) result = cls(
result.__dict__.update(state) 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 return result
def asJSONState(self): def asJSONState(self):
result = self.__dict__.copy() return {
del result['getDuration'] x: getattr(self, x)
return result for x in self.__slots__
if x not in self._IGNORE_IN_STATE
}
def _APDEXDateDictAsJSONState(date_dict): def _APDEXDateDictAsJSONState(date_dict):
return { return {
...@@ -352,10 +370,16 @@ def _APDEXDateDictAsJSONState(date_dict): ...@@ -352,10 +370,16 @@ def _APDEXDateDictAsJSONState(date_dict):
} }
class GenericSiteStats: class GenericSiteStats:
def __init__(self, threshold, getDuration, suffix, error_detail=False, def __init__(
user_agent_detail=False, self,
# Non-generic parameters threshold,
**_): getDuration,
suffix,
error_detail=False,
user_agent_detail=False,
# Non-generic parameters
**_
):
self.threshold = threshold self.threshold = threshold
self.suffix = suffix self.suffix = suffix
self.error_detail = error_detail self.error_detail = error_detail
...@@ -491,8 +515,13 @@ class GenericSiteStats: ...@@ -491,8 +515,13 @@ class GenericSiteStats:
@classmethod @classmethod
def fromJSONState(cls, state, getDuration, suffix): def fromJSONState(cls, state, getDuration, suffix):
error_detail = state['error_detail'] error_detail = state['error_detail']
result = cls(state['threshold'], getDuration, suffix, error_detail, result = cls(
state.get('user_agent_detail', True)) threshold=state['threshold'],
getDuration=getDuration,
suffix=suffix,
error_detail=error_detail,
user_agent_detail=state.get('user_agent_detail', True),
)
if error_detail: if error_detail:
error_url_count = result.error_url_count error_url_count = result.error_url_count
for state_status, state_url_dict in state['error_url_count'].items(): for state_status, state_url_dict in state['error_url_count'].items():
...@@ -550,10 +579,22 @@ class ERP5SiteStats(GenericSiteStats): ...@@ -550,10 +579,22 @@ class ERP5SiteStats(GenericSiteStats):
- If a line belongs to a module and has at least 2 slashes after module, - 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 count line as belonging to a document of that module
""" """
def __init__(self, threshold, getDuration, suffix, error_detail=False, def __init__(
user_agent_detail=False, erp5_expand_other=False): self,
super().__init__(threshold, getDuration, suffix, threshold,
error_detail=error_detail, user_agent_detail=user_agent_detail) 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 self.expand_other = erp5_expand_other
...@@ -1127,7 +1168,7 @@ def asHTML( ...@@ -1127,7 +1168,7 @@ def asHTML(
for caption, value in ( for caption, value in (
('apdex threshold', f'{args.apdex:.2f}s'), ('apdex threshold', f'{args.apdex:.2f}s'),
('period', args.period or (period + ' (auto)')), ('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'<tr><th class="text">{caption}</th><td>{value}</td></tr>')
out.write(f'</table><h2>Hits per {period}</h2><table class="stats">' out.write(f'</table><h2>Hits per {period}</h2><table class="stats">'
...@@ -1615,9 +1656,13 @@ def main(): ...@@ -1615,9 +1656,13 @@ def main():
try: try:
site_data = per_site[site] site_data = per_site[site]
except KeyError: except KeyError:
site_data = per_site[site] = action(threshold, getDuration, site_data = per_site[site] = action(
error_detail=error_detail, user_agent_detail=user_agent_detail, threshold=threshold,
erp5_expand_other=erp5_expand_other) getDuration=getDuration,
error_detail=error_detail,
user_agent_detail=user_agent_detail,
erp5_expand_other=erp5_expand_other,
)
try: try:
site_data.accumulate(match, url_match, hit_date) site_data.accumulate(match, url_match, hit_date)
except Exception: # pylint: disable=broad-exception-caught except Exception: # pylint: disable=broad-exception-caught
...@@ -1658,16 +1703,5 @@ def main(): ...@@ -1658,16 +1703,5 @@ def main():
site_caption_dict, site_caption_dict,
) )
if __name__ == '__main__': def getResource(name, encoding='utf-8'):
__resource_base = os.path.join(*os.path.split(__file__)[:-1]) return pkgutil.get_data(__name__, name).decode(encoding)
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)
##############################################################################
#
# 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