summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIan Cordasco <graffatcolmingov@gmail.com>2016-06-19 09:25:27 -0500
committerIan Cordasco <graffatcolmingov@gmail.com>2016-06-19 20:37:32 -0500
commitadedd6c5cfd705d01e79a0f673ed95ee4bb26e08 (patch)
tree6022d1645bc08ac2227a35e759634e758dac49bd
parent1f7a8081ad14ecfc304bf46e37504dbcb241a339 (diff)
downloadflake8-adedd6c5cfd705d01e79a0f673ed95ee4bb26e08.tar.gz
Add --benchmark and formatting for its values
-rw-r--r--flake8/checker.py60
-rw-r--r--flake8/defaults.py8
-rw-r--r--flake8/formatting/base.py19
-rw-r--r--flake8/main/application.py19
-rw-r--r--flake8/main/options.py7
-rw-r--r--flake8/processor.py5
6 files changed, 112 insertions, 6 deletions
diff --git a/flake8/checker.py b/flake8/checker.py
index 655387e..b875f44 100644
--- a/flake8/checker.py
+++ b/flake8/checker.py
@@ -10,6 +10,12 @@ try:
except ImportError:
multiprocessing = None
+try:
+ import Queue as queue
+except ImportError:
+ import queue
+
+from flake8 import defaults
from flake8 import exceptions
from flake8 import processor
from flake8 import utils
@@ -72,14 +78,22 @@ class Manager(object):
self.jobs = self._job_count()
self.process_queue = None
self.results_queue = None
+ self.statistics_queue = None
self.using_multiprocessing = self.jobs > 1
self.processes = []
self.checkers = []
+ self.statistics = {
+ 'files': 0,
+ 'logical lines': 0,
+ 'physical lines': 0,
+ 'tokens': 0,
+ }
if self.using_multiprocessing:
try:
self.process_queue = multiprocessing.Queue()
self.results_queue = multiprocessing.Queue()
+ self.statistics_queue = multiprocessing.Queue()
except OSError as oserr:
if oserr.errno not in SERIAL_RETRY_ERRNOS:
raise
@@ -96,6 +110,29 @@ class Manager(object):
proc.join(0.2)
self._cleanup_queue(self.process_queue)
self._cleanup_queue(self.results_queue)
+ self._cleanup_queue(self.statistics_queue)
+
+ def _process_statistics(self):
+ all_statistics = self.statistics
+ if self.using_multiprocessing:
+ total_number_of_checkers = len(self.checkers)
+ statistics_gathered = 0
+ while statistics_gathered < total_number_of_checkers:
+ try:
+ statistics = self.statistics_queue.get(block=False)
+ statistics_gathered += 1
+ except queue.Empty:
+ break
+
+ for statistic in defaults.STATISTIC_NAMES:
+ all_statistics[statistic] += statistics[statistic]
+ else:
+ statistics_generator = (checker.statistics
+ for checker in self.checkers)
+ for statistics in statistics_generator:
+ for statistic in defaults.STATISTIC_NAMES:
+ all_statistics[statistic] += statistics[statistic]
+ all_statistics['files'] += len(self.checkers)
def _job_count(self):
# type: () -> Union[int, NoneType]
@@ -182,7 +219,7 @@ class Manager(object):
LOG.info('Running checks in parallel')
for checker in iter(self.process_queue.get, 'DONE'):
LOG.debug('Running checker for file "%s"', checker.filename)
- checker.run_checks(self.results_queue)
+ checker.run_checks(self.results_queue, self.statistics_queue)
self.results_queue.put('DONE')
def is_path_excluded(self, path):
@@ -280,7 +317,7 @@ class Manager(object):
def run_serial(self):
"""Run the checkers in serial."""
for checker in self.checkers:
- checker.run_checks(self.results_queue)
+ checker.run_checks(self.results_queue, self.statistics_queue)
def run(self):
"""Run all the checkers.
@@ -325,6 +362,7 @@ class Manager(object):
def stop(self):
"""Stop checking files."""
+ self._process_statistics()
for proc in self.processes:
LOG.info('Joining %s to the main process', proc.name)
proc.join()
@@ -342,12 +380,21 @@ class FileChecker(object):
The plugins registered to check the file.
:type checks:
flake8.plugins.manager.Checkers
+ :param style_guide:
+ The initialized StyleGuide for this particular run.
+ :type style_guide:
+ flake8.style_guide.StyleGuide
"""
self.filename = filename
self.checks = checks
self.style_guide = style_guide
self.results = []
self.processor = self._make_processor()
+ self.statistics = {
+ 'tokens': 0,
+ 'logical lines': 0,
+ 'physical lines': len(self.processor.lines),
+ }
def _make_processor(self):
try:
@@ -466,8 +513,10 @@ class FileChecker(object):
:meth:`flake8.checker.FileChecker.run_checks`.
"""
parens = 0
+ statistics = self.statistics
file_processor = self.processor
for token in file_processor.generate_tokens():
+ statistics['tokens'] += 1
self.check_physical_eol(token)
token_type, text = token[0:2]
processor.log_token(LOG, token)
@@ -485,7 +534,7 @@ class FileChecker(object):
self.run_physical_checks(file_processor.lines[-1])
self.run_logical_checks()
- def run_checks(self, results_queue):
+ def run_checks(self, results_queue, statistics_queue):
"""Run checks against the file."""
if self.processor.should_ignore_file():
return
@@ -501,6 +550,11 @@ class FileChecker(object):
if results_queue is not None:
results_queue.put((self.filename, self.results))
+ logical_lines = self.processor.statistics['logical lines']
+ self.statistics['logical lines'] = logical_lines
+ if statistics_queue is not None:
+ statistics_queue.put(self.statistics)
+
def handle_comment(self, token, token_text):
"""Handle the logic when encountering a comment token."""
# The comment also ends a physical line
diff --git a/flake8/defaults.py b/flake8/defaults.py
index 76829a5..d9f5a0b 100644
--- a/flake8/defaults.py
+++ b/flake8/defaults.py
@@ -7,5 +7,11 @@ MAX_LINE_LENGTH = 79
TRUTHY_VALUES = set(['true', '1', 't'])
-# Other consants
+# Other constants
WHITESPACE = frozenset(' \t')
+
+STATISTIC_NAMES = (
+ 'logical lines',
+ 'physical lines',
+ 'tokens',
+)
diff --git a/flake8/formatting/base.py b/flake8/formatting/base.py
index 19e21e4..4fda6f4 100644
--- a/flake8/formatting/base.py
+++ b/flake8/formatting/base.py
@@ -87,7 +87,24 @@ class BaseFormatter(object):
def show_benchmarks(self, benchmarks):
"""Format and print the benchmarks."""
- pass
+ # NOTE(sigmavirus24): The format strings are a little confusing, even
+ # to me, so here's a quick explanation:
+ # We specify the named value first followed by a ':' to indicate we're
+ # formatting the value.
+ # Next we use '<' to indicate we want the value left aligned.
+ # Then '10' is the width of the area.
+ # For floats, finally, we only want only want at most 3 digits after
+ # the decimal point to be displayed. This is the precision and it
+ # can not be specified for integers which is why we need two separate
+ # format strings.
+ float_format = '{value:<10.3} {statistic}'.format
+ int_format = '{value:<10} {statistic}'.format
+ for statistic, value in benchmarks:
+ if isinstance(value, int):
+ benchmark = int_format(statistic=statistic, value=value)
+ else:
+ benchmark = float_format(statistic=statistic, value=value)
+ self._write(benchmark)
def show_source(self, error):
"""Show the physical line generating the error.
diff --git a/flake8/main/application.py b/flake8/main/application.py
index 6739216..efb1049 100644
--- a/flake8/main/application.py
+++ b/flake8/main/application.py
@@ -7,6 +7,7 @@ import time
import flake8
from flake8 import checker
+from flake8 import defaults
from flake8 import style_guide
from flake8 import utils
from flake8.main import options
@@ -225,6 +226,22 @@ class Application(object):
self.file_checker_manager.run()
LOG.info('Finished running')
self.file_checker_manager.stop()
+ self.end_time = time.time()
+
+ def report_benchmarks(self):
+ if not self.options.benchmark:
+ return
+ time_elapsed = self.end_time - self.start_time
+ statistics = [('seconds elapsed', time_elapsed)]
+ add_statistic = statistics.append
+ for statistic in (defaults.STATISTIC_NAMES + ('files',)):
+ value = self.file_checker_manager.statistics[statistic]
+ total_description = 'total ' + statistic + ' processed'
+ add_statistic((total_description, value))
+ per_second_description = statistic + ' processed per second'
+ add_statistic((per_second_description, int(value / time_elapsed)))
+
+ self.formatter.show_benchmarks(statistics)
def report_errors(self):
# type: () -> NoneType
@@ -259,7 +276,7 @@ class Application(object):
self.initialize(argv)
self.run_checks()
self.report_errors()
- self.end_time = time.time()
+ self.report_benchmarks()
def run(self, argv=None):
# type: (Union[NoneType, List[str]]) -> NoneType
diff --git a/flake8/main/options.py b/flake8/main/options.py
index 51db34e..c725c38 100644
--- a/flake8/main/options.py
+++ b/flake8/main/options.py
@@ -192,3 +192,10 @@ def register_default_options(option_manager):
'--isolated', default=False, action='store_true',
help='Ignore all found configuration files.',
)
+
+ # Benchmarking
+
+ add_option(
+ '--benchmark', default=False, action='store_true',
+ help='Print benchmark information about this run of Flake8',
+ )
diff --git a/flake8/processor.py b/flake8/processor.py
index 8e6d897..0c33cc2 100644
--- a/flake8/processor.py
+++ b/flake8/processor.py
@@ -97,6 +97,10 @@ class FileProcessor(object):
self.total_lines = len(self.lines)
#: Verbosity level of Flake8
self.verbose = options.verbose
+ #: Statistics dictionary
+ self.statistics = {
+ 'logical lines': 0,
+ }
@contextlib.contextmanager
def inside_multiline(self, line_number):
@@ -186,6 +190,7 @@ class FileProcessor(object):
"""Build a logical line from the current tokens list."""
comments, logical, mapping_list = self.build_logical_line_tokens()
self.logical_line = ''.join(logical)
+ self.statistics['logical lines'] += 1
return ''.join(comments), self.logical_line, mapping_list
def split_line(self, token):