From 4b04a58b3d7476f0761e4e57d941ee955a63396e Mon Sep 17 00:00:00 2001 From: Johan Vergeer Date: Sun, 26 Jul 2020 14:20:24 +0200 Subject: [PATCH 1/8] Add .idea directory for developers working with PyCharm --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index b6e47617d..872c981f6 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,5 @@ dmypy.json # Pyre type checker .pyre/ + +.idea From a2d5ca51017696f0e0895783688326f74b77f169 Mon Sep 17 00:00:00 2001 From: Johan Vergeer Date: Sun, 26 Jul 2020 14:20:50 +0200 Subject: [PATCH 2/8] Add Pipfile.lock so all developers use the same dependencies --- Pipfile.lock | 162 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 Pipfile.lock diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 000000000..9576061da --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,162 @@ +{ + "_meta": { + "hash": { + "sha256": "4dff7e92495c39b0b3a6cc1f37d724fd4c488ace6a778d5e18dfbc99aa82adc6" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.6" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "colorama": { + "hashes": [ + "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", + "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1" + ], + "version": "==0.4.3" + }, + "commonmark": { + "hashes": [ + "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60", + "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9" + ], + "version": "==0.9.1" + }, + "numpy": { + "hashes": [ + "sha256:082f8d4dd69b6b688f64f509b91d482362124986d98dc7dc5f5e9f9b9c3bb983", + "sha256:1bc0145999e8cb8aed9d4e65dd8b139adf1919e521177f198529687dbf613065", + "sha256:309cbcfaa103fc9a33ec16d2d62569d541b79f828c382556ff072442226d1968", + "sha256:3673c8b2b29077f1b7b3a848794f8e11f401ba0b71c49fbd26fb40b71788b132", + "sha256:480fdd4dbda4dd6b638d3863da3be82873bba6d32d1fc12ea1b8486ac7b8d129", + "sha256:56ef7f56470c24bb67fb43dae442e946a6ce172f97c69f8d067ff8550cf782ff", + "sha256:5a936fd51049541d86ccdeef2833cc89a18e4d3808fe58a8abeb802665c5af93", + "sha256:5b6885c12784a27e957294b60f97e8b5b4174c7504665333c5e94fbf41ae5d6a", + "sha256:667c07063940e934287993366ad5f56766bc009017b4a0fe91dbd07960d0aba7", + "sha256:7ed448ff4eaffeb01094959b19cbaf998ecdee9ef9932381420d514e446601cd", + "sha256:8343bf67c72e09cfabfab55ad4a43ce3f6bf6e6ced7acf70f45ded9ebb425055", + "sha256:92feb989b47f83ebef246adabc7ff3b9a59ac30601c3f6819f8913458610bdcc", + "sha256:935c27ae2760c21cd7354402546f6be21d3d0c806fffe967f745d5f2de5005a7", + "sha256:aaf42a04b472d12515debc621c31cf16c215e332242e7a9f56403d814c744624", + "sha256:b12e639378c741add21fbffd16ba5ad25c0a1a17cf2b6fe4288feeb65144f35b", + "sha256:b1cca51512299841bf69add3b75361779962f9cee7d9ee3bb446d5982e925b69", + "sha256:b8456987b637232602ceb4d663cb34106f7eb780e247d51a260b84760fd8f491", + "sha256:b9792b0ac0130b277536ab8944e7b754c69560dac0415dd4b2dbd16b902c8954", + "sha256:c9591886fc9cbe5532d5df85cb8e0cc3b44ba8ce4367bd4cf1b93dc19713da72", + "sha256:cf1347450c0b7644ea142712619533553f02ef23f92f781312f6a3553d031fc7", + "sha256:de8b4a9b56255797cbddb93281ed92acbc510fb7b15df3f01bd28f46ebc4edae", + "sha256:e1b1dc0372f530f26a03578ac75d5e51b3868b9b76cd2facba4c9ee0eb252ab1", + "sha256:e45f8e981a0ab47103181773cc0a54e650b2aef8c7b6cd07405d0fa8d869444a", + "sha256:e4f6d3c53911a9d103d8ec9518190e52a8b945bab021745af4939cfc7c0d4a9e", + "sha256:ed8a311493cf5480a2ebc597d1e177231984c818a86875126cfd004241a73c3e", + "sha256:ef71a1d4fd4858596ae80ad1ec76404ad29701f8ca7cdcebc50300178db14dfc" + ], + "index": "pypi", + "version": "==1.19.1" + }, + "pprintpp": { + "hashes": [ + "sha256:b6b4dcdd0c0c0d75e4d7b2f21a9e933e5b2ce62b26e1a54537f9651ae5a5c01d", + "sha256:ea826108e2c7f49dc6d66c752973c3fc9749142a798d6b254e1e301cfdbc6403" + ], + "version": "==0.4.0" + }, + "pygments": { + "hashes": [ + "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44", + "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324" + ], + "version": "==2.6.1" + }, + "rich": { + "hashes": [ + "sha256:09582169934f778acb3505292200b06be69177b86335265ac3566987f7e822cf", + "sha256:4e2673c02d25be6a75ebbeab4b2563b19a0532d309b86ddae6700b7466482a51" + ], + "index": "pypi", + "version": "==4.0.0" + }, + "typing-extensions": { + "hashes": [ + "sha256:6e95524d8a547a91e08f404ae485bbb71962de46967e1b71a0cb89af24e761c5", + "sha256:79ee589a3caca649a9bfd2a8de4709837400dfa00b6cc81962a1e6a1815969ae", + "sha256:f8d2bd89d25bc39dabe7d23df520442fa1d8969b82544370e03d88b5a591c392" + ], + "version": "==3.7.4.2" + } + }, + "develop": { + "attrs": { + "hashes": [ + "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", + "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" + ], + "version": "==19.3.0" + }, + "more-itertools": { + "hashes": [ + "sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5", + "sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2" + ], + "version": "==8.4.0" + }, + "packaging": { + "hashes": [ + "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8", + "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181" + ], + "version": "==20.4" + }, + "pluggy": { + "hashes": [ + "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", + "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" + ], + "version": "==0.13.1" + }, + "py": { + "hashes": [ + "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2", + "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342" + ], + "version": "==1.9.0" + }, + "pyparsing": { + "hashes": [ + "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", + "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" + ], + "version": "==2.4.7" + }, + "pytest": { + "hashes": [ + "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1", + "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8" + ], + "index": "pypi", + "version": "==5.4.3" + }, + "six": { + "hashes": [ + "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", + "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" + ], + "version": "==1.15.0" + }, + "wcwidth": { + "hashes": [ + "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784", + "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83" + ], + "version": "==0.2.5" + } + } +} From 7b7cc943858025f9cac22554aa87424a562c1cca Mon Sep 17 00:00:00 2001 From: Johan Vergeer Date: Sun, 26 Jul 2020 14:25:26 +0200 Subject: [PATCH 3/8] Move Adaptive class from scalene.py to adaptive.py, which removes the duplicate --- MANIFEST.in | 2 +- scalene/adaptive.py | 14 ++------------ scalene/scalene.py | 42 +++++++----------------------------------- 3 files changed, 10 insertions(+), 48 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 4ef174c1e..29159fe0a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,7 +3,7 @@ include LICENSE include scalene/libscalene.so include scalene/libscalene.dylib include scalene/scalene.py -include scalene/adaptive.py +include scalene/Adaptive.py include scalene/sparkline.py include test/testme.py diff --git a/scalene/adaptive.py b/scalene/adaptive.py index 5dc67d3cd..cd3e778f2 100644 --- a/scalene/adaptive.py +++ b/scalene/adaptive.py @@ -1,18 +1,12 @@ -import math -import random from typing import List -class adaptive: +class Adaptive: """Implements sampling to achieve the effect of a uniform random sample.""" sample_array: List[float] = [] current_index = 0 - total_samples = 0 max_samples = 0 - count_average = 0 - sum_average = 0 - max_average = 1 def __init__(self, size: int): self.max_samples = size @@ -22,7 +16,6 @@ def __init__(self, size: int): def add(self, value: float) -> None: if self.current_index >= self.max_samples: # Decimate - # print("DECIMATION " + str(self.sample_array)) new_array = [0.0] * self.max_samples for i in range(0, self.max_samples // 3): arr = [self.sample_array[i * 3 + j] for j in range(0, 3)] @@ -30,11 +23,8 @@ def add(self, value: float) -> None: new_array[i] = arr[1] # Median self.current_index = self.max_samples // 3 self.sample_array = new_array - # print("POST DECIMATION = " + str(self.sample_array)) - # Update average length - self.max_average *= 3 self.sample_array[self.current_index] = value - self.current_index += 1 # count_average += 1 + self.current_index += 1 def get(self) -> List[float]: return self.sample_array diff --git a/scalene/scalene.py b/scalene/scalene.py index c0d3c48ac..5bd09c669 100644 --- a/scalene/scalene.py +++ b/scalene/scalene.py @@ -54,37 +54,7 @@ cast, ) - -class adaptive: - """Implements sampling to achieve the effect of a uniform random sample.""" - - sample_array: List[float] = [] - current_index = 0 - max_samples = 0 - - def __init__(self, size: int): - self.max_samples = size - # must be a power of two - self.sample_array = [0] * size - - def add(self, value: float) -> None: - if self.current_index >= self.max_samples: - # Decimate - new_array = [0.0] * self.max_samples - for i in range(0, self.max_samples // 3): - arr = [self.sample_array[i * 3 + j] for j in range(0, 3)] - arr.sort() - new_array[i] = arr[1] # Median - self.current_index = self.max_samples // 3 - self.sample_array = new_array - self.sample_array[self.current_index] = value - self.current_index += 1 - - def get(self) -> List[float]: - return self.sample_array - - def len(self) -> int: - return self.current_index +from scalene.adaptive import Adaptive # Sparkline stuff @@ -104,8 +74,10 @@ def len(self) -> int: # ▁▂▃▄▅▆▇█ bar = "".join([chr(i) for i in range(9601, 9609)]) + barcount = len(bar) + # From https://rosettacode.org/wiki/Sparkline_in_unicode#Python def sparkline( numbers: List[float], fixed_min: float = -1, fixed_max: float = -1 @@ -456,12 +428,12 @@ def new_subprocess_Popen(args, bufsize=-1, executable=None, stdin=None, stdout=N # when did we last receive a signal? __last_signal_time: float = 0 - # memory footprint samples (time, footprint), using 'adaptive' sampling. - __memory_footprint_samples = adaptive(27) + # memory footprint samples (time, footprint), using 'Adaptive' sampling. + __memory_footprint_samples = Adaptive(27) # same, but per line - __per_line_footprint_samples: Dict[str, Dict[int, adaptive]] = defaultdict( - lambda: defaultdict(lambda: adaptive(9)) + __per_line_footprint_samples: Dict[str, Dict[int, Adaptive]] = defaultdict( + lambda: defaultdict(lambda: Adaptive(9)) ) # path for the program being profiled From 055680f969b1bf16fde7ce196d1ea63659ae479d Mon Sep 17 00:00:00 2001 From: Johan Vergeer Date: Sun, 26 Jul 2020 15:42:12 +0200 Subject: [PATCH 4/8] Extract Sparkline class --- scalene/scalene.py | 50 +++------------------ scalene/sparkline.py | 101 ++++++++++++++++++++++++------------------- 2 files changed, 63 insertions(+), 88 deletions(-) diff --git a/scalene/scalene.py b/scalene/scalene.py index 5bd09c669..538b33cd4 100644 --- a/scalene/scalene.py +++ b/scalene/scalene.py @@ -55,49 +55,9 @@ ) from scalene.adaptive import Adaptive +from scalene.sparkline import SparkLine -# Sparkline stuff - -# Check if we are in Windows Subsystem for Linux and *not* using -# the highly recommended Windows Terminal -# (https://aka.ms/windowsterminal) -if "WSL_DISTRO_NAME" in os.environ and "WT_PROFILE_ID" not in os.environ: - # We are running in the Windows Subsystem for Linux Display, a - # crappy version of the sparkline because the Windows console - # *still* only properly displays IBM Code page 437 by default. - # ▄▄■■■■▀▀ - bar = chr(0x2584) * 2 + chr(0x25A0) * 3 + chr(0x2580) * 3 -else: - # Reasonable system. Use Unicode characters. - # Unicode: 9601, 9602, 9603, 9604, 9605, 9606, 9607, 9608 - # ▁▂▃▄▅▆▇█ - bar = "".join([chr(i) for i in range(9601, 9609)]) - - -barcount = len(bar) - - -# From https://rosettacode.org/wiki/Sparkline_in_unicode#Python -def sparkline( - numbers: List[float], fixed_min: float = -1, fixed_max: float = -1 -) -> Tuple[float, float, str]: - if fixed_min == -1: - mn = float(min(numbers)) - else: - mn = fixed_min - if fixed_max == -1: - mx = float(max(numbers)) - else: - mx = fixed_max - extent = mx - mn - if extent == 0: - extent = 1 - sparkstr = "".join( - bar[min([barcount - 1, int((n - mn) / extent * barcount)])] for n in numbers - ) - return mn, mx, sparkstr - # Logic to ignore @profile decorators. try: @@ -1012,7 +972,9 @@ def stop() -> None: @staticmethod def generate_sparkline( - arr: List[float], minimum: float = -1, maximum: float = -1 + arr: List[float], + minimum: Optional[float] = None, + maximum: Optional[float] = None ) -> Tuple[float, float, str]: """Produces a sparkline, as in ▁▁▁▁▁▂▃▂▄▅▄▆█▆█▆""" iterations = len(arr) @@ -1021,8 +983,8 @@ def generate_sparkline( return 0, 0, "" # Prevent negative memory output due to sampling error. samples = [i if i > 0 else 0 for i in arr] - minval, maxval, sp_line = sparkline(samples[0:iterations], minimum, maximum) - return minval, maxval, sp_line + sl = SparkLine() + return sl.create(samples[0:iterations], minimum, maximum) @staticmethod def output_profile_line( diff --git a/scalene/sparkline.py b/scalene/sparkline.py index 0ca319703..3c8da4725 100644 --- a/scalene/sparkline.py +++ b/scalene/sparkline.py @@ -1,44 +1,57 @@ -from typing import List, Tuple -from os import environ - -# Sparkline stuff - -# Check if we are in Windows Subsystem for Linux and *not* using -# the highly recommended Windows Terminal -# (https://aka.ms/windowsterminal) -if "WSL_DISTRO_NAME" in environ and "WT_PROFILE_ID" not in environ: - # We are running in the Windows Subsystem for Linux Display, a - # crappy version of the sparkline because the Windows console - # *still* only properly displays IBM Code page 437 by default. - # ▄▄■■■■▀▀ - bar = chr(0x2584) * 2 + chr(0x25A0) * 3 + chr(0x2580) * 3 -else: - # Reasonable system. Use Unicode characters. - # Unicode: 9601, 9602, 9603, 9604, 9605, 9606, 9607, 9608 - # ▁▂▃▄▅▆▇█ - bar = "".join([chr(i) for i in range(9601, 9609)]) - -barcount = len(bar) - -# From https://rosettacode.org/wiki/Sparkline_in_unicode#Python -def sparkline( - numbers: List[float], fixed_min: float = -1, fixed_max: float = -1 -) -> Tuple[float, float, str]: - if fixed_min == -1: - mn = float(min(numbers)) - else: - mn = fixed_min - if fixed_max == -1: - mx = float(max(numbers)) - else: - mx = fixed_max - # print(numbers) - # mn, mx = min(numbers), max(numbers) - extent = mx - mn - if extent == 0: - extent = 1 - # print("mn, mx = " + str(mn) + ", " + str(mx) + " extent = " + str(extent)) - sparkstr = "".join( - bar[min([barcount - 1, int((n - mn) / extent * barcount)])] for n in numbers - ) - return mn, mx, sparkstr +import os +from typing import List, Tuple, Optional + + +class SparkLine: + """Produces a sparkline, as in ▁▁▁▁▁▂▃▂▄▅▄▆█▆█▆""" + def __init__(self): + self.__bars = self._get_bar() + self.__bar_count = len(self.__bars) + + # From https://rosettacode.org/wiki/Sparkline_in_unicode#Python + def create( + self, + numbers: List[float], + fixed_min: Optional[float] = None, + fixed_max: Optional[float] = None, + ) -> Tuple[float, float, str]: + min_ = fixed_min if fixed_min is not None else float(min(numbers)) + max_ = fixed_max if fixed_max is not None else float(max(numbers)) + extent = self._get_extent(max_, min_) + spark = "".join( + self.__bars[ + min([self.__bar_count - 1, int((n - min_) / extent * self.__bar_count)]) + ] + for n in numbers + ) + return min_, max_, spark + + def _get_extent(self, max_, min_): + extent = max_ - min_ + if extent == 0: + extent = 1 + return extent + + def _get_bar(self) -> str: + if self._in_wsl() and not self._in_windows_terminal(): + # We are running in the Windows Subsystem for Linux Display, a + # crappy version of the sparkline because the Windows console + # *still* only properly displays IBM Code page 437 by default. + # ▄▄■■■■▀▀ + return chr(0x2584) * 2 + chr(0x25A0) * 3 + chr(0x2580) * 3 + else: + # Reasonable system. Use Unicode characters. + # Unicode: 9601, 9602, 9603, 9604, 9605, 9606, 9607, 9608 + # ▁▂▃▄▅▆▇█ + return "".join([chr(i) for i in range(9601, 9609)]) + + def _in_wsl(self) -> bool: + """Are we in Windows Subsystem for Linux?""" + return "WSL_DISTRO_NAME" in os.environ + + def _in_windows_terminal(self) -> bool: + """Are we in Windows Terminal? + + https://aka.ms/windowsterminal + """ + return "WT_PROFILE_ID" in os.environ From 8b25a882ca99472fdbce01b9a0ee6875b6813015 Mon Sep 17 00:00:00 2001 From: Johan Vergeer Date: Sun, 26 Jul 2020 15:42:28 +0200 Subject: [PATCH 5/8] Create Sparkline tests --- test/test_sparkline.py | 69 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 test/test_sparkline.py diff --git a/test/test_sparkline.py b/test/test_sparkline.py new file mode 100644 index 000000000..50d4c55cc --- /dev/null +++ b/test/test_sparkline.py @@ -0,0 +1,69 @@ +import pytest + +from scalene.sparkline import SparkLine + + +@pytest.fixture(name="sl") +def sparkline() -> SparkLine: + return SparkLine() + + +def test_get_bar(sl): + bar = sl._get_bar() + + assert bar == "▁▂▃▄▅▆▇█" + + +def test_get_bar___in_wsl(sl, monkeypatch): + monkeypatch.setenv("WSL_DISTRO_NAME", "Some WSL distro name") + bar = sl._get_bar() + + assert bar == "▄▄■■■▀▀▀" + + +def test_get_bar__in_wsl_and_windows_terminal(sl, monkeypatch): + monkeypatch.setenv("WSL_DISTRO_NAME", "Some WSL distro name") + monkeypatch.setenv("WT_PROFILE_ID", "Some Windows Terminal id") + bar = sl._get_bar() + + assert bar == "▁▂▃▄▅▆▇█" + + +def test_create(sl): + numbers = [1, 2, 3, 4, 5, 6, 7, 8] + + result = sl.create(numbers) + + assert result == (1, 8, "▁▂▃▄▅▆▇█") + + +def test_create__up_and_down(sl): + numbers = [1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 5, 4, 3, 2, 1] + + result = sl.create(numbers) + + assert result == (1, 8, "▁▂▃▄▅▆▇█▇▆▅▄▃▂▁") + + +def test_create__with_min(sl): + numbers = [1, 2, 3, 4, 5, 6, 7, 8] + + result = sl.create(numbers, fixed_min=0) + + assert result == (0, 8.0, '▂▃▄▅▆▇██') + + +def test_create__with_max(sl): + numbers = [1, 2, 3, 4, 5, 6, 7, 8] + + result = sl.create(numbers, fixed_max=6) + + assert result == (1.0, 8, '▁▂▃▄▅▆▇█') + + +def test_create__with_max_below_actual_max(sl): + numbers = [1, 2, 3, 4, 5, 6, 7, 8] + + result = sl.create(numbers, fixed_max=6) + + assert result == (1.0, 6, '▁▂▄▅▇███') From e3a0a61c265a60993d198c0faad8c4bc4fdf94e0 Mon Sep 17 00:00:00 2001 From: Johan Vergeer Date: Sun, 26 Jul 2020 15:44:48 +0200 Subject: [PATCH 6/8] Create Sparkline tests --- scalene/sparkline.py | 10 ++++++---- test/test_sparkline.py | 6 +++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/scalene/sparkline.py b/scalene/sparkline.py index 3c8da4725..844dc7e9f 100644 --- a/scalene/sparkline.py +++ b/scalene/sparkline.py @@ -3,12 +3,14 @@ class SparkLine: - """Produces a sparkline, as in ▁▁▁▁▁▂▃▂▄▅▄▆█▆█▆""" + """Produces a sparkline, as in ▁▁▁▁▁▂▃▂▄▅▄▆█▆█▆ + + From https://rosettacode.org/wiki/Sparkline_in_unicode#Python + """ def __init__(self): - self.__bars = self._get_bar() + self.__bars = self._get_bars() self.__bar_count = len(self.__bars) - # From https://rosettacode.org/wiki/Sparkline_in_unicode#Python def create( self, numbers: List[float], @@ -32,7 +34,7 @@ def _get_extent(self, max_, min_): extent = 1 return extent - def _get_bar(self) -> str: + def _get_bars(self) -> str: if self._in_wsl() and not self._in_windows_terminal(): # We are running in the Windows Subsystem for Linux Display, a # crappy version of the sparkline because the Windows console diff --git a/test/test_sparkline.py b/test/test_sparkline.py index 50d4c55cc..09da742f7 100644 --- a/test/test_sparkline.py +++ b/test/test_sparkline.py @@ -9,14 +9,14 @@ def sparkline() -> SparkLine: def test_get_bar(sl): - bar = sl._get_bar() + bar = sl._get_bars() assert bar == "▁▂▃▄▅▆▇█" def test_get_bar___in_wsl(sl, monkeypatch): monkeypatch.setenv("WSL_DISTRO_NAME", "Some WSL distro name") - bar = sl._get_bar() + bar = sl._get_bars() assert bar == "▄▄■■■▀▀▀" @@ -24,7 +24,7 @@ def test_get_bar___in_wsl(sl, monkeypatch): def test_get_bar__in_wsl_and_windows_terminal(sl, monkeypatch): monkeypatch.setenv("WSL_DISTRO_NAME", "Some WSL distro name") monkeypatch.setenv("WT_PROFILE_ID", "Some Windows Terminal id") - bar = sl._get_bar() + bar = sl._get_bars() assert bar == "▁▂▃▄▅▆▇█" From 662c37c9b51eb495356db81cc05f2ed3c9072f91 Mon Sep 17 00:00:00 2001 From: Johan Vergeer Date: Sun, 26 Jul 2020 16:14:14 +0200 Subject: [PATCH 7/8] Move generate method to Sparkline class --- scalene/scalene.py | 20 ++------------------ scalene/sparkline.py | 17 ++++++++++++++++- test/test_sparkline.py | 42 +++++++++++++++++++++++++++++------------- 3 files changed, 47 insertions(+), 32 deletions(-) diff --git a/scalene/scalene.py b/scalene/scalene.py index 538b33cd4..ace5eef72 100644 --- a/scalene/scalene.py +++ b/scalene/scalene.py @@ -970,22 +970,6 @@ def stop() -> None: Scalene.disable_signals() Scalene.__elapsed_time += Scalene.gettime() - Scalene.__start_time - @staticmethod - def generate_sparkline( - arr: List[float], - minimum: Optional[float] = None, - maximum: Optional[float] = None - ) -> Tuple[float, float, str]: - """Produces a sparkline, as in ▁▁▁▁▁▂▃▂▄▅▄▆█▆█▆""" - iterations = len(arr) - all_zeros = all(i == 0 for i in arr) - if all_zeros: - return 0, 0, "" - # Prevent negative memory output due to sampling error. - samples = [i if i > 0 else 0 for i in arr] - sl = SparkLine() - return sl.create(samples[0:iterations], minimum, maximum) - @staticmethod def output_profile_line( fname: Filename, line_no: LineNumber, line: str, console: Console, tbl: Table, @@ -1072,7 +1056,7 @@ def output_profile_line( for i in range(0, len(samples.get())): samples.get()[i] *= n_usage_fraction if samples.get(): - _, _, spark_str = Scalene.generate_sparkline( + _, _, spark_str = SparkLine().generate( samples.get()[0 : samples.len()], 0, current_max ) @@ -1158,7 +1142,7 @@ def output_profiles() -> bool: samples = Scalene.__memory_footprint_samples if len(samples.get()) > 0: # Output a sparkline as a summary of memory usage over time. - _, _, spark_str = Scalene.generate_sparkline( + _, _, spark_str = SparkLine().generate( samples.get()[0 : samples.len()], 0, current_max ) mem_usage_line = Text.assemble( diff --git a/scalene/sparkline.py b/scalene/sparkline.py index 844dc7e9f..64352a1fa 100644 --- a/scalene/sparkline.py +++ b/scalene/sparkline.py @@ -7,11 +7,26 @@ class SparkLine: From https://rosettacode.org/wiki/Sparkline_in_unicode#Python """ + def __init__(self): self.__bars = self._get_bars() self.__bar_count = len(self.__bars) - def create( + def generate( + self, + arr: List[float], + minimum: Optional[float] = None, + maximum: Optional[float] = None, + ) -> Tuple[float, float, str]: + all_zeros = all(i == 0 for i in arr) + if all_zeros: + return 0, 0, "" + + # Prevent negative memory output due to sampling error. + samples = [i if i > 0 else 0 for i in arr] + return self._create(samples[0:len(arr)], minimum, maximum) + + def _create( self, numbers: List[float], fixed_min: Optional[float] = None, diff --git a/test/test_sparkline.py b/test/test_sparkline.py index 09da742f7..352a2736f 100644 --- a/test/test_sparkline.py +++ b/test/test_sparkline.py @@ -8,20 +8,20 @@ def sparkline() -> SparkLine: return SparkLine() -def test_get_bar(sl): +def test_get_bars(sl): bar = sl._get_bars() assert bar == "▁▂▃▄▅▆▇█" -def test_get_bar___in_wsl(sl, monkeypatch): +def test_get_bars___in_wsl(sl, monkeypatch): monkeypatch.setenv("WSL_DISTRO_NAME", "Some WSL distro name") bar = sl._get_bars() assert bar == "▄▄■■■▀▀▀" -def test_get_bar__in_wsl_and_windows_terminal(sl, monkeypatch): +def test_get_bars__in_wsl_and_windows_terminal(sl, monkeypatch): monkeypatch.setenv("WSL_DISTRO_NAME", "Some WSL distro name") monkeypatch.setenv("WT_PROFILE_ID", "Some Windows Terminal id") bar = sl._get_bars() @@ -29,41 +29,57 @@ def test_get_bar__in_wsl_and_windows_terminal(sl, monkeypatch): assert bar == "▁▂▃▄▅▆▇█" -def test_create(sl): +def test_generate(sl): numbers = [1, 2, 3, 4, 5, 6, 7, 8] - result = sl.create(numbers) + result = sl.generate(numbers) assert result == (1, 8, "▁▂▃▄▅▆▇█") -def test_create__up_and_down(sl): +def test_generate__up_and_down(sl): numbers = [1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 5, 4, 3, 2, 1] - result = sl.create(numbers) + result = sl.generate(numbers) assert result == (1, 8, "▁▂▃▄▅▆▇█▇▆▅▄▃▂▁") -def test_create__with_min(sl): +def test_generate__all_zeroes(sl): + numbers = [0, 0, 0] + + result = sl.generate(numbers) + + assert result == (0, 0, '') + + +def test_generate__with_negative_values(sl): + numbers = [1, 2, 3, -4, 5, -6, 7, 8] + + result = sl.generate(numbers) + + assert result == (0.0, 8.0, '▂▃▄▁▆▁██') + + +def test_generate__with_min(sl): numbers = [1, 2, 3, 4, 5, 6, 7, 8] - result = sl.create(numbers, fixed_min=0) + result = sl.generate(numbers, minimum=0) assert result == (0, 8.0, '▂▃▄▅▆▇██') -def test_create__with_max(sl): +def test_generate__with_max_same_as_actual_max(sl): numbers = [1, 2, 3, 4, 5, 6, 7, 8] - result = sl.create(numbers, fixed_max=6) + result = sl.generate(numbers, maximum=8) assert result == (1.0, 8, '▁▂▃▄▅▆▇█') -def test_create__with_max_below_actual_max(sl): +def test_generate__with_max_below_actual_max(sl): numbers = [1, 2, 3, 4, 5, 6, 7, 8] - result = sl.create(numbers, fixed_max=6) + result = sl.generate(numbers, maximum=6) assert result == (1.0, 6, '▁▂▄▅▇███') From 7fd6cb7db410eeb2fd20009f8b66273766cdd827 Mon Sep 17 00:00:00 2001 From: Johan Vergeer Date: Sun, 26 Jul 2020 16:40:31 +0200 Subject: [PATCH 8/8] Add pyperf dev dependency for tests --- Pipfile | 1 + Pipfile.lock | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Pipfile b/Pipfile index 48d480f8e..6b1709143 100644 --- a/Pipfile +++ b/Pipfile @@ -4,6 +4,7 @@ url = "https://pypi.org/simple" verify_ssl = true [dev-packages] +pyperf = "*" pytest = "*" [packages] diff --git a/Pipfile.lock b/Pipfile.lock index 9576061da..9c377af32 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "4dff7e92495c39b0b3a6cc1f37d724fd4c488ace6a778d5e18dfbc99aa82adc6" + "sha256": "45235d3073d9d2de47a63cc5c5195ab6fae6f27c620ab34906b80de54cbe7531" }, "pipfile-spec": 6, "requires": { @@ -78,11 +78,11 @@ }, "rich": { "hashes": [ - "sha256:09582169934f778acb3505292200b06be69177b86335265ac3566987f7e822cf", - "sha256:4e2673c02d25be6a75ebbeab4b2563b19a0532d309b86ddae6700b7466482a51" + "sha256:710aea2fbee45acaa4eea181d9f22856f3262cc4a5e8f9fa29661e5fb9a98c9f", + "sha256:e3ad462f3933dd86cab7036c62fde736ba41590b074dc5a0ff5a907f15ecf128" ], "index": "pypi", - "version": "==4.0.0" + "version": "==4.1.0" }, "typing-extensions": { "hashes": [ @@ -136,6 +136,14 @@ ], "version": "==2.4.7" }, + "pyperf": { + "hashes": [ + "sha256:2189fbc4af08d519f85468e70e32c902eab0f1341b2c41028b94b2832d3169a7", + "sha256:839192bafba79893fd2ec51d346561ac951d304ef03069faeecec063a58aff52" + ], + "index": "pypi", + "version": "==2.0.0" + }, "pytest": { "hashes": [ "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1",