transcrypto 1.3.0__py3-none-any.whl → 1.5.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- transcrypto/aes.py +10 -2
- transcrypto/base.py +720 -103
- transcrypto/constants.py +1921 -0
- transcrypto/dsa.py +16 -2
- transcrypto/modmath.py +37 -419
- transcrypto/profiler.py +191 -0
- transcrypto/safetrans.py +1231 -0
- transcrypto/transcrypto.py +14 -180
- transcrypto-1.5.1.dist-info/METADATA +1071 -0
- transcrypto-1.5.1.dist-info/RECORD +18 -0
- transcrypto-1.3.0.dist-info/METADATA +0 -2562
- transcrypto-1.3.0.dist-info/RECORD +0 -15
- {transcrypto-1.3.0.dist-info → transcrypto-1.5.1.dist-info}/WHEEL +0 -0
- {transcrypto-1.3.0.dist-info → transcrypto-1.5.1.dist-info}/licenses/LICENSE +0 -0
- {transcrypto-1.3.0.dist-info → transcrypto-1.5.1.dist-info}/top_level.txt +0 -0
transcrypto/profiler.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2025 Daniel Balparda (balparda@github.com) - Apache-2.0 license
|
|
4
|
+
#
|
|
5
|
+
"""Balparda's TransCrypto Profiler command line interface.
|
|
6
|
+
|
|
7
|
+
See README.md for documentation on how to use.
|
|
8
|
+
|
|
9
|
+
Notes on the layout (quick mental model):
|
|
10
|
+
|
|
11
|
+
primes
|
|
12
|
+
dsa
|
|
13
|
+
doc md
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import logging
|
|
20
|
+
# import pdb
|
|
21
|
+
import sys
|
|
22
|
+
from typing import Callable
|
|
23
|
+
|
|
24
|
+
from . import base, modmath, dsa
|
|
25
|
+
|
|
26
|
+
__author__ = 'balparda@github.com'
|
|
27
|
+
__version__: str = base.__version__ # version comes from base!
|
|
28
|
+
__version_tuple__: tuple[int, ...] = base.__version_tuple__
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _BuildParser() -> argparse.ArgumentParser: # pylint: disable=too-many-statements,too-many-locals
|
|
32
|
+
"""Construct the CLI argument parser (kept in sync with the docs)."""
|
|
33
|
+
# ========================= main parser ==========================================================
|
|
34
|
+
parser: argparse.ArgumentParser = argparse.ArgumentParser(
|
|
35
|
+
prog='poetry run profiler',
|
|
36
|
+
description=('profiler: CLI for TransCrypto Profiler, measure library performance.'),
|
|
37
|
+
epilog=(
|
|
38
|
+
'Examples:\n\n'
|
|
39
|
+
' # --- Primes ---\n'
|
|
40
|
+
' poetry run profiler -p -n 10 primes\n'
|
|
41
|
+
' poetry run profiler -n 20 dsa\n'
|
|
42
|
+
),
|
|
43
|
+
formatter_class=argparse.RawTextHelpFormatter)
|
|
44
|
+
sub = parser.add_subparsers(dest='command')
|
|
45
|
+
|
|
46
|
+
# ========================= global flags =========================================================
|
|
47
|
+
# -v/-vv/-vvv/-vvvv for ERROR/WARN/INFO/DEBUG
|
|
48
|
+
parser.add_argument(
|
|
49
|
+
'-v', '--verbose', action='count', default=0,
|
|
50
|
+
help='Increase verbosity (use -v/-vv/-vvv/-vvvv for ERROR/WARN/INFO/DEBUG)')
|
|
51
|
+
|
|
52
|
+
thread_grp = parser.add_mutually_exclusive_group()
|
|
53
|
+
thread_grp.add_argument(
|
|
54
|
+
'-s', '--serial', action='store_true',
|
|
55
|
+
help='If test can be serial, do it like that with no parallelization (default)')
|
|
56
|
+
thread_grp.add_argument(
|
|
57
|
+
'-p', '--parallel', action='store_true',
|
|
58
|
+
help='If test can be parallelized into processes, do it like that')
|
|
59
|
+
|
|
60
|
+
parser.add_argument(
|
|
61
|
+
'-n', '--number', type=int, default=15,
|
|
62
|
+
help='Number of experiments (repeats) for every measurement')
|
|
63
|
+
parser.add_argument(
|
|
64
|
+
'-c', '--confidence', type=int, default=98,
|
|
65
|
+
help=('Confidence level to evaluate measurements at as int percentage points [50,99], '
|
|
66
|
+
'inclusive, representing 50% to 99%'))
|
|
67
|
+
|
|
68
|
+
parser.add_argument(
|
|
69
|
+
'-b', '--bits', type=str, default='1000,9000,1000',
|
|
70
|
+
help=('Bit lengths to investigate as "int,int,int"; behaves like arguments for range(), '
|
|
71
|
+
'i.e., "start,stop,step", eg. "1000,3000,500" will investigate 1000,1500,2000,2500'))
|
|
72
|
+
|
|
73
|
+
# ========================= Prime Generation =====================================================
|
|
74
|
+
|
|
75
|
+
# Regular prime generation
|
|
76
|
+
sub.add_parser(
|
|
77
|
+
'primes',
|
|
78
|
+
help='Measure regular prime generation.',
|
|
79
|
+
epilog=('-n 30 -b 9000,11000,1000 primes\nStarting SERIAL regular primes test\n'
|
|
80
|
+
'9000 → 38.88 s ± 14.74 s [24.14 s … 53.63 s]98%CI@30\n'
|
|
81
|
+
'10000 → 41.26 s ± 22.82 s [18.44 s … 1.07 min]98%CI@30\nFinished in 40.07 min'))
|
|
82
|
+
|
|
83
|
+
# DSA primes generation
|
|
84
|
+
sub.add_parser(
|
|
85
|
+
'dsa',
|
|
86
|
+
help='Measure DSA prime generation.',
|
|
87
|
+
epilog=('-p -n 2 -b 1000,1500,100 -c 80 dsa\nStarting PARALLEL DSA primes test\n'
|
|
88
|
+
'1000 → 236.344 ms ± 273.236 ms [*0.00 s … 509.580 ms]80%CI@2\n'
|
|
89
|
+
'1100 → 319.308 ms ± 639.775 ms [*0.00 s … 959.083 ms]80%CI@2\n'
|
|
90
|
+
'1200 → 523.885 ms ± 879.981 ms [*0.00 s … 1.40 s]80%CI@2\n'
|
|
91
|
+
'1300 → 506.285 ms ± 687.153 ms [*0.00 s … 1.19 s]80%CI@2\n'
|
|
92
|
+
'1400 → 552.840 ms ± 47.012 ms [505.828 ms … 599.852 ms]80%CI@2\nFinished in 4.12 s'))
|
|
93
|
+
|
|
94
|
+
# ========================= Markdown Generation ==================================================
|
|
95
|
+
|
|
96
|
+
# Documentation generation
|
|
97
|
+
doc: argparse.ArgumentParser = sub.add_parser(
|
|
98
|
+
'doc', help='Documentation utilities. (Not for regular use: these are developer utils.)')
|
|
99
|
+
doc_sub = doc.add_subparsers(dest='doc_command')
|
|
100
|
+
doc_sub.add_parser(
|
|
101
|
+
'md',
|
|
102
|
+
help='Emit Markdown docs for the CLI (see README.md section "Creating a New Version").',
|
|
103
|
+
epilog='doc md > profiler.md\n<<saves file>>')
|
|
104
|
+
|
|
105
|
+
return parser
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _PrimeProfiler(
|
|
109
|
+
prime_callable: Callable[[int], int],
|
|
110
|
+
repeats: int, n_bits_range: tuple[int, int, int], confidence: float, /) -> None:
|
|
111
|
+
primes: dict[int, list[float]] = {}
|
|
112
|
+
for n_bits in range(*n_bits_range):
|
|
113
|
+
# investigate for size n_bits
|
|
114
|
+
primes[n_bits] = []
|
|
115
|
+
for _ in range(repeats):
|
|
116
|
+
with base.Timer(emit_log=False) as tmr:
|
|
117
|
+
pr: int = prime_callable(n_bits)
|
|
118
|
+
assert pr and pr.bit_length() == n_bits
|
|
119
|
+
primes[n_bits].append(tmr.elapsed)
|
|
120
|
+
# finished collecting n_bits-sized primes
|
|
121
|
+
measurements: str = base.HumanizedMeasurements(
|
|
122
|
+
primes[n_bits], parser=base.HumanizedSeconds, confidence=confidence)
|
|
123
|
+
print(f'{n_bits} → {measurements}')
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def main(argv: list[str] | None = None, /) -> int: # pylint: disable=invalid-name,too-many-locals,too-many-branches,too-many-statements
|
|
127
|
+
"""Main entry point."""
|
|
128
|
+
# build the parser and parse args
|
|
129
|
+
parser: argparse.ArgumentParser = _BuildParser()
|
|
130
|
+
args: argparse.Namespace = parser.parse_args(argv)
|
|
131
|
+
# take care of global options
|
|
132
|
+
levels: list[int] = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
|
|
133
|
+
logging.basicConfig(
|
|
134
|
+
level=levels[min(args.verbose, len(levels) - 1)], # type: ignore
|
|
135
|
+
format=getattr(base, 'LOG_FORMAT', '%(levelname)s:%(message)s'))
|
|
136
|
+
logging.captureWarnings(True)
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
# get the command, do basic checks and switch
|
|
140
|
+
command: str = args.command.lower().strip() if args.command else ''
|
|
141
|
+
repeats: int = 1 if args.number < 1 else args.number
|
|
142
|
+
confidence: int = 55 if args.confidence < 55 else args.confidence
|
|
143
|
+
confidence = 99 if confidence > 99 else confidence
|
|
144
|
+
args.serial = True if (not args.serial and not args.parallel) else args.serial # make default
|
|
145
|
+
bits: tuple[int, ...] = tuple(int(x, 10) for x in args.bits.strip().split(','))
|
|
146
|
+
if len(bits) != 3:
|
|
147
|
+
raise base.InputError('-b/--bits should be 3 ints, like: start,stop,step; eg.: 1000,3000,500')
|
|
148
|
+
with base.Timer(emit_log=False) as tmr:
|
|
149
|
+
match command:
|
|
150
|
+
# -------- Primes ----------
|
|
151
|
+
case 'primes':
|
|
152
|
+
print(f'Starting {"SERIAL" if args.serial else "PARALLEL"} regular primes test')
|
|
153
|
+
_PrimeProfiler(
|
|
154
|
+
lambda n: modmath.NBitRandomPrimes(n, serial=args.serial, n_primes=1).pop(),
|
|
155
|
+
repeats, bits, confidence / 100.0)
|
|
156
|
+
|
|
157
|
+
case 'dsa':
|
|
158
|
+
print(f'Starting {"SERIAL" if args.serial else "PARALLEL"} DSA primes test')
|
|
159
|
+
_PrimeProfiler(
|
|
160
|
+
lambda n: dsa.NBitRandomDSAPrimes(n, n // 2, serial=args.serial)[0],
|
|
161
|
+
repeats, bits, confidence / 100.0)
|
|
162
|
+
|
|
163
|
+
# -------- Documentation ----------
|
|
164
|
+
case 'doc':
|
|
165
|
+
doc_command: str = (
|
|
166
|
+
args.doc_command.lower().strip() if getattr(args, 'doc_command', '') else '')
|
|
167
|
+
match doc_command:
|
|
168
|
+
case 'md':
|
|
169
|
+
print(base.GenerateCLIMarkdown(
|
|
170
|
+
'profiler', _BuildParser(), description=(
|
|
171
|
+
'`profiler` is a command-line utility that provides stats on TransCrypto '
|
|
172
|
+
'performance.')))
|
|
173
|
+
case _:
|
|
174
|
+
raise NotImplementedError()
|
|
175
|
+
|
|
176
|
+
case _:
|
|
177
|
+
parser.print_help()
|
|
178
|
+
|
|
179
|
+
if command not in ('doc',):
|
|
180
|
+
print(f'Finished in {tmr}')
|
|
181
|
+
|
|
182
|
+
except NotImplementedError as err:
|
|
183
|
+
print(f'Invalid command: {err}')
|
|
184
|
+
except (base.Error, ValueError) as err:
|
|
185
|
+
print(str(err))
|
|
186
|
+
|
|
187
|
+
return 0
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
if __name__ == '__main__':
|
|
191
|
+
sys.exit(main())
|