trench-mark 1.0.0__tar.gz
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.
- trench_mark-1.0.0/PKG-INFO +7 -0
- trench_mark-1.0.0/README.md +0 -0
- trench_mark-1.0.0/pyproject.toml +17 -0
- trench_mark-1.0.0/setup.cfg +4 -0
- trench_mark-1.0.0/src/trench-mark/__init__.py +0 -0
- trench_mark-1.0.0/src/trench-mark/cli.py +373 -0
- trench_mark-1.0.0/src/trench_mark.egg-info/PKG-INFO +7 -0
- trench_mark-1.0.0/src/trench_mark.egg-info/SOURCES.txt +10 -0
- trench_mark-1.0.0/src/trench_mark.egg-info/dependency_links.txt +1 -0
- trench_mark-1.0.0/src/trench_mark.egg-info/entry_points.txt +2 -0
- trench_mark-1.0.0/src/trench_mark.egg-info/requires.txt +1 -0
- trench_mark-1.0.0/src/trench_mark.egg-info/top_level.txt +1 -0
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "trench-mark"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Automated TensorRT performance profiling for Jetson edge devices."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"pyyaml",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.scripts]
|
|
16
|
+
# Terminal Command = "python_folder_name.file_name:function_name"
|
|
17
|
+
trench-mark = "trench_mark.cli:main"
|
|
File without changes
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
import threading
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
import yaml
|
|
10
|
+
|
|
11
|
+
class MemoryProfiler:
|
|
12
|
+
def __init__(self):
|
|
13
|
+
self.running = False
|
|
14
|
+
self.max_ram = 0
|
|
15
|
+
self.thread = None
|
|
16
|
+
self.process = None
|
|
17
|
+
|
|
18
|
+
def start(self):
|
|
19
|
+
self.running = True
|
|
20
|
+
self.thread = threading.Thread(target=self._monitor, daemon=True)
|
|
21
|
+
self.thread.start()
|
|
22
|
+
|
|
23
|
+
def _monitor(self):
|
|
24
|
+
if not shutil.which("tegrastats"):
|
|
25
|
+
if logger:
|
|
26
|
+
logger.write("Warning: 'tegrastats' binary not found. Memory profiling disabled.")
|
|
27
|
+
return
|
|
28
|
+
try:
|
|
29
|
+
self.process = subprocess.Popen(
|
|
30
|
+
["tegrastats"],
|
|
31
|
+
stdout=subprocess.PIPE,
|
|
32
|
+
stderr=subprocess.STDOUT,
|
|
33
|
+
text=True,
|
|
34
|
+
bufsize=1,
|
|
35
|
+
)
|
|
36
|
+
while self.running:
|
|
37
|
+
line = self.process.stdout.readline()
|
|
38
|
+
if not line:
|
|
39
|
+
break
|
|
40
|
+
match = re.search(r"RAM\s+(\d+)/\d+MB", line)
|
|
41
|
+
if match:
|
|
42
|
+
ram = int(match.group(1))
|
|
43
|
+
self.max_ram = max(self.max_ram, ram)
|
|
44
|
+
except Exception as e:
|
|
45
|
+
if logger:
|
|
46
|
+
logger.write(f"Memory profiler error: {e}")
|
|
47
|
+
|
|
48
|
+
def stop(self):
|
|
49
|
+
self.running = False
|
|
50
|
+
if self.process:
|
|
51
|
+
try:
|
|
52
|
+
self.process.terminate()
|
|
53
|
+
except Exception:
|
|
54
|
+
pass
|
|
55
|
+
if self.thread and self.thread.is_alive():
|
|
56
|
+
self.thread.join(timeout=2)
|
|
57
|
+
return self.max_ram
|
|
58
|
+
|
|
59
|
+
class Logger:
|
|
60
|
+
def __init__(self, log_dir="logs"):
|
|
61
|
+
self.log_dir = log_dir
|
|
62
|
+
os.makedirs(self.log_dir, exist_ok=True)
|
|
63
|
+
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
|
64
|
+
self.log_file = os.path.join(self.log_dir, f"benchmark_{timestamp}.log")
|
|
65
|
+
with open(self.log_file, "w", encoding="utf-8") as f:
|
|
66
|
+
f.write("=" * 90 + "\n")
|
|
67
|
+
f.write("TENSORRT BENCHMARK LOG\n")
|
|
68
|
+
f.write("=" * 90 + "\n")
|
|
69
|
+
f.write(f"Started: {datetime.now()}\n")
|
|
70
|
+
f.write(f"Working Directory: {os.getcwd()}\n")
|
|
71
|
+
f.write("=" * 90 + "\n\n")
|
|
72
|
+
|
|
73
|
+
def write(self, message):
|
|
74
|
+
try:
|
|
75
|
+
with open(self.log_file, "a", encoding="utf-8") as f:
|
|
76
|
+
f.write(message)
|
|
77
|
+
if not message.endswith("\n"):
|
|
78
|
+
f.write("\n")
|
|
79
|
+
except Exception:
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
def command(self, cmd, output, returncode):
|
|
83
|
+
self.write("\n" + "=" * 90)
|
|
84
|
+
self.write("COMMAND")
|
|
85
|
+
self.write("=" * 90)
|
|
86
|
+
self.write(" ".join(cmd))
|
|
87
|
+
self.write("\nRETURN CODE")
|
|
88
|
+
self.write("=" * 90)
|
|
89
|
+
self.write(str(returncode))
|
|
90
|
+
self.write("\nOUTPUT")
|
|
91
|
+
self.write("=" * 90)
|
|
92
|
+
self.write(output if output else "[No output]")
|
|
93
|
+
self.write("\n" + "=" * 90 + "\n")
|
|
94
|
+
|
|
95
|
+
logger = None
|
|
96
|
+
|
|
97
|
+
def run_command(cmd, dry_run=False):
|
|
98
|
+
global logger
|
|
99
|
+
if logger:
|
|
100
|
+
logger.write(f"\nExecuting command: {' '.join(cmd)}")
|
|
101
|
+
|
|
102
|
+
if dry_run:
|
|
103
|
+
simulated_output = (
|
|
104
|
+
f"[DRY-RUN] Command validated: {' '.join(cmd)}\n"
|
|
105
|
+
"&&&& PASSED TensorRT.trtexec [DRY-RUN]\n"
|
|
106
|
+
"mean: 12.50 ms\n"
|
|
107
|
+
"Throughput: 80.00 qps\n"
|
|
108
|
+
)
|
|
109
|
+
if logger:
|
|
110
|
+
logger.command(cmd, simulated_output, 0)
|
|
111
|
+
return simulated_output
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
result = subprocess.run(
|
|
115
|
+
cmd,
|
|
116
|
+
stdout=subprocess.PIPE,
|
|
117
|
+
stderr=subprocess.STDOUT,
|
|
118
|
+
text=True,
|
|
119
|
+
)
|
|
120
|
+
output = result.stdout
|
|
121
|
+
if logger:
|
|
122
|
+
logger.command(cmd, output, result.returncode)
|
|
123
|
+
return output
|
|
124
|
+
except Exception as e:
|
|
125
|
+
error = f"Command execution failed: {e}"
|
|
126
|
+
if logger:
|
|
127
|
+
logger.command(cmd, error, -1)
|
|
128
|
+
return error
|
|
129
|
+
|
|
130
|
+
def extract_metrics(log):
|
|
131
|
+
latency_match = re.search(r"mean\s*[=:]\s*([\d\.]+)\s*ms", log, re.IGNORECASE)
|
|
132
|
+
throughput_match = re.search(r"Throughput\s*[=:]\s*([\d\.]+)\s*qps", log, re.IGNORECASE)
|
|
133
|
+
latency = float(latency_match.group(1)) if latency_match else None
|
|
134
|
+
throughput = float(throughput_match.group(1)) if throughput_match else None
|
|
135
|
+
return {"latency_ms": latency, "fps": throughput}
|
|
136
|
+
|
|
137
|
+
def log_system_information():
|
|
138
|
+
global logger
|
|
139
|
+
if not logger:
|
|
140
|
+
return
|
|
141
|
+
logger.write("\n" + "=" * 90)
|
|
142
|
+
logger.write("SYSTEM INFORMATION")
|
|
143
|
+
logger.write("=" * 90)
|
|
144
|
+
commands = [
|
|
145
|
+
["uname", "-a"],
|
|
146
|
+
["cat", "/etc/nv_tegra_release"],
|
|
147
|
+
["python3", "--version"],
|
|
148
|
+
["trtexec", "--help"],
|
|
149
|
+
]
|
|
150
|
+
for cmd in commands:
|
|
151
|
+
try:
|
|
152
|
+
result = subprocess.run(
|
|
153
|
+
cmd,
|
|
154
|
+
stdout=subprocess.PIPE,
|
|
155
|
+
stderr=subprocess.STDOUT,
|
|
156
|
+
text=True,
|
|
157
|
+
)
|
|
158
|
+
logger.command(cmd, result.stdout, result.returncode)
|
|
159
|
+
except Exception as e:
|
|
160
|
+
logger.write(f"Failed to execute {' '.join(cmd)}: {e}")
|
|
161
|
+
|
|
162
|
+
def parse_arguments():
|
|
163
|
+
parser = argparse.ArgumentParser(
|
|
164
|
+
prog="trt-benchmark",
|
|
165
|
+
description="Automated, memory-safe TensorRT profiling pipeline for Jetson edge devices.",
|
|
166
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
167
|
+
)
|
|
168
|
+
parser.add_argument("-m", "--model", type=str, default=None, help="Path to the ONNX model file.")
|
|
169
|
+
parser.add_argument("-c", "--config", type=str, default="config.yaml", help="Path to config YAML file (used if CLI options are omitted).")
|
|
170
|
+
parser.add_argument("-s", "--input-shape", type=str, default="3x640x640", help="Input tensor shape in CHW format (e.g., 3x640x640).")
|
|
171
|
+
parser.add_argument("--input-name", type=str, default="images", help="Input binding name in the ONNX model.")
|
|
172
|
+
parser.add_argument("-p", "--precisions", nargs="+", choices=["fp32", "fp16", "int8"], default=["fp32", "fp16", "int8"], help="Precision levels to evaluate.")
|
|
173
|
+
parser.add_argument("-b", "--batch-sizes", nargs="+", type=int, default=[1, 4, 8], help="Batch sizes to benchmark.")
|
|
174
|
+
parser.add_argument("-i", "--iterations", type=int, default=500, help="Number of inference cycles per run.")
|
|
175
|
+
parser.add_argument("-w", "--warmup", type=int, default=50, help="Warmup duration in milliseconds.")
|
|
176
|
+
parser.add_argument("--workspace", type=int, default=2048, help="Max workspace size in MB for TensorRT builder.")
|
|
177
|
+
parser.add_argument("--dry-run", action="store_true", help="Simulate execution without running trtexec or compiling engines.")
|
|
178
|
+
return parser.parse_args()
|
|
179
|
+
|
|
180
|
+
def load_configuration(args):
|
|
181
|
+
config = {
|
|
182
|
+
"model_name": None,
|
|
183
|
+
"onnx_path": None,
|
|
184
|
+
"input_shape": args.input_shape,
|
|
185
|
+
"input_name": args.input_name,
|
|
186
|
+
"batch_sizes": args.batch_sizes,
|
|
187
|
+
"precisions": args.precisions,
|
|
188
|
+
"iterations": args.iterations,
|
|
189
|
+
"warmup": args.warmup,
|
|
190
|
+
"workspace": args.workspace,
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if args.model:
|
|
194
|
+
config["onnx_path"] = args.model
|
|
195
|
+
config["model_name"] = os.path.splitext(os.path.basename(args.model))[0]
|
|
196
|
+
return config
|
|
197
|
+
|
|
198
|
+
if os.path.exists(args.config):
|
|
199
|
+
try:
|
|
200
|
+
with open(args.config, "r", encoding="utf-8") as f:
|
|
201
|
+
file_cfg = yaml.safe_load(f)
|
|
202
|
+
config["model_name"] = file_cfg.get("model", {}).get("name", "model")
|
|
203
|
+
config["onnx_path"] = file_cfg.get("model", {}).get("onnx_path")
|
|
204
|
+
config["input_shape"] = file_cfg.get("model", {}).get("input_shape", args.input_shape)
|
|
205
|
+
config["batch_sizes"] = file_cfg.get("benchmarks", {}).get("batch_sizes", args.batch_sizes)
|
|
206
|
+
config["precisions"] = file_cfg.get("benchmarks", {}).get("precisions", args.precisions)
|
|
207
|
+
config["iterations"] = file_cfg.get("settings", {}).get("iterations", args.iterations)
|
|
208
|
+
config["warmup"] = file_cfg.get("settings", {}).get("warmup", args.warmup)
|
|
209
|
+
return config
|
|
210
|
+
except Exception as e:
|
|
211
|
+
if logger:
|
|
212
|
+
logger.write(f"Failed to read {args.config}: {e}")
|
|
213
|
+
|
|
214
|
+
return config
|
|
215
|
+
|
|
216
|
+
def main():
|
|
217
|
+
global logger
|
|
218
|
+
args = parse_arguments()
|
|
219
|
+
logger = Logger()
|
|
220
|
+
config = load_configuration(args)
|
|
221
|
+
|
|
222
|
+
if not config["onnx_path"]:
|
|
223
|
+
error_msg = f"No ONNX model specified. Provide --model path/to/model.onnx or ensure valid config file exists at '{args.config}'."
|
|
224
|
+
print(f"Error: {error_msg}")
|
|
225
|
+
logger.write(f"FATAL: {error_msg}")
|
|
226
|
+
sys.exit(1)
|
|
227
|
+
|
|
228
|
+
if not args.dry_run and not os.path.exists(config["onnx_path"]):
|
|
229
|
+
error_msg = f"ONNX file '{config['onnx_path']}' does not exist."
|
|
230
|
+
print(f"Error: {error_msg}")
|
|
231
|
+
logger.write(f"FATAL: {error_msg}")
|
|
232
|
+
sys.exit(1)
|
|
233
|
+
|
|
234
|
+
if not args.dry_run:
|
|
235
|
+
log_system_information()
|
|
236
|
+
|
|
237
|
+
model_name = config["model_name"]
|
|
238
|
+
onnx_path = config["onnx_path"]
|
|
239
|
+
batch_sizes = sorted(config["batch_sizes"])
|
|
240
|
+
precisions = config["precisions"]
|
|
241
|
+
iterations = config["iterations"]
|
|
242
|
+
warmup = config["warmup"]
|
|
243
|
+
input_name = config["input_name"]
|
|
244
|
+
input_shape = config["input_shape"]
|
|
245
|
+
workspace = config["workspace"]
|
|
246
|
+
|
|
247
|
+
# FIX 1: Strip accidental '1x' if passed via CLI or YAML
|
|
248
|
+
if input_shape.startswith("1x"):
|
|
249
|
+
input_shape = input_shape[2:]
|
|
250
|
+
|
|
251
|
+
min_shape = f"{input_name}:1x{input_shape}"
|
|
252
|
+
opt_shape = f"{input_name}:1x{input_shape}"
|
|
253
|
+
max_batch = max(batch_sizes)
|
|
254
|
+
max_shape = f"{input_name}:{max_batch}x{input_shape}"
|
|
255
|
+
|
|
256
|
+
# FIX 2: Isolate engine binaries in a models directory
|
|
257
|
+
models_dir = "models"
|
|
258
|
+
os.makedirs(models_dir, exist_ok=True)
|
|
259
|
+
|
|
260
|
+
print("\nTENSORRT EDGE BENCHMARK")
|
|
261
|
+
print(f"Target Model: {onnx_path}")
|
|
262
|
+
if args.dry_run:
|
|
263
|
+
print("[DRY-RUN MODE ENABLED: Simulating commands]")
|
|
264
|
+
print("=" * 75)
|
|
265
|
+
print(f"{'Precision':<10} | {'Batch':<7} | {'Latency (ms)':<15} | {'Throughput (FPS)':<18} | {'Peak RAM (MB)'}")
|
|
266
|
+
print("-" * 75)
|
|
267
|
+
|
|
268
|
+
logger.write("\nBenchmark Configuration Details:")
|
|
269
|
+
logger.write(f"Model: {model_name}")
|
|
270
|
+
logger.write(f"ONNX Path: {onnx_path}")
|
|
271
|
+
logger.write(f"Input Name: {input_name}")
|
|
272
|
+
logger.write(f"Input Shape: {input_shape}")
|
|
273
|
+
logger.write(f"Shape Profile: MIN={min_shape}, OPT={opt_shape}, MAX={max_shape}")
|
|
274
|
+
logger.write(f"Batch Matrix: {batch_sizes}")
|
|
275
|
+
logger.write(f"Precisions: {precisions}")
|
|
276
|
+
logger.write(f"Iterations: {iterations}, Warmup: {warmup} ms\n")
|
|
277
|
+
|
|
278
|
+
for precision in precisions:
|
|
279
|
+
engine_path = os.path.join(models_dir, f"{model_name}_{precision}.engine")
|
|
280
|
+
|
|
281
|
+
# FIX 3: 0-Byte engine check to prevent deserialization crashes
|
|
282
|
+
is_corrupted = not args.dry_run and os.path.exists(engine_path) and os.path.getsize(engine_path) == 0
|
|
283
|
+
|
|
284
|
+
if not os.path.exists(engine_path) or is_corrupted or args.dry_run:
|
|
285
|
+
logger.write("\n" + "#" * 90)
|
|
286
|
+
logger.write(f"BUILDING {precision.upper()} ENGINE")
|
|
287
|
+
logger.write("#" * 90)
|
|
288
|
+
|
|
289
|
+
print(f"Building {precision.upper()} engine... (Saving to {engine_path})")
|
|
290
|
+
|
|
291
|
+
# FIX 4: Jetson Memory Safety & Faster Compilation Flags
|
|
292
|
+
build_cmd = [
|
|
293
|
+
"trtexec",
|
|
294
|
+
f"--onnx={onnx_path}",
|
|
295
|
+
f"--saveEngine={engine_path}",
|
|
296
|
+
f"--minShapes={min_shape}",
|
|
297
|
+
f"--optShapes={opt_shape}",
|
|
298
|
+
f"--maxShapes={max_shape}",
|
|
299
|
+
"--tempdir=.",
|
|
300
|
+
"--tempfileControls=in_memory:deny,temporary:allow",
|
|
301
|
+
f"--memPoolSize=workspace:{workspace}",
|
|
302
|
+
"--builderOptimizationLevel=1",
|
|
303
|
+
"--timingCacheFile=trt.cache"
|
|
304
|
+
]
|
|
305
|
+
|
|
306
|
+
if precision == "fp16":
|
|
307
|
+
build_cmd.append("--fp16")
|
|
308
|
+
elif precision == "int8":
|
|
309
|
+
build_cmd.extend(["--int8", "--fp16"])
|
|
310
|
+
|
|
311
|
+
run_command(build_cmd, dry_run=args.dry_run)
|
|
312
|
+
|
|
313
|
+
if not args.dry_run and (not os.path.exists(engine_path) or os.path.getsize(engine_path) == 0):
|
|
314
|
+
print(f"{precision.upper()} engine build failed or returned 0 bytes.")
|
|
315
|
+
logger.write(f"\n{precision.upper()} ENGINE BUILD FAILED.")
|
|
316
|
+
continue
|
|
317
|
+
|
|
318
|
+
logger.write(f"\n{precision.upper()} engine ready: {engine_path}")
|
|
319
|
+
else:
|
|
320
|
+
logger.write(f"\nFound existing valid {precision.upper()} engine in {models_dir}/. Skipping compilation.")
|
|
321
|
+
|
|
322
|
+
for bs in batch_sizes:
|
|
323
|
+
logger.write("\n" + "-" * 90)
|
|
324
|
+
logger.write(f"RUNNING INFERENCE | Precision={precision.upper()} | Batch={bs}")
|
|
325
|
+
logger.write("-" * 90)
|
|
326
|
+
|
|
327
|
+
shape = f"{input_name}:{bs}x{input_shape}"
|
|
328
|
+
infer_cmd = [
|
|
329
|
+
"trtexec",
|
|
330
|
+
f"--loadEngine={engine_path}",
|
|
331
|
+
f"--shapes={shape}",
|
|
332
|
+
f"--iterations={iterations}",
|
|
333
|
+
f"--warmUp={warmup}",
|
|
334
|
+
"--noDataTransfers",
|
|
335
|
+
]
|
|
336
|
+
|
|
337
|
+
profiler = MemoryProfiler()
|
|
338
|
+
if not args.dry_run:
|
|
339
|
+
profiler.start()
|
|
340
|
+
|
|
341
|
+
infer_log = run_command(infer_cmd, dry_run=args.dry_run)
|
|
342
|
+
|
|
343
|
+
peak_ram = profiler.stop() if not args.dry_run else 0
|
|
344
|
+
metrics = extract_metrics(infer_log)
|
|
345
|
+
|
|
346
|
+
logger.write(f"\nExtracted metrics for {precision.upper()} Batch {bs}:")
|
|
347
|
+
logger.write(f"Latency: {metrics['latency_ms']}")
|
|
348
|
+
logger.write(f"Throughput: {metrics['fps']}")
|
|
349
|
+
logger.write(f"Peak RAM: {peak_ram} MB")
|
|
350
|
+
|
|
351
|
+
if metrics["latency_ms"] is not None and metrics["fps"] is not None:
|
|
352
|
+
print(f"{precision.upper():<10} | {bs:<7} | {metrics['latency_ms']:<15.2f} | {metrics['fps']:<18.2f} | {peak_ram}")
|
|
353
|
+
else:
|
|
354
|
+
print(f"{precision.upper():<10} | {bs:<7} | {'FAILED':<15} | {'FAILED':<18} | {peak_ram}")
|
|
355
|
+
logger.write("\nMetric extraction failed. Full trtexec output saved above.")
|
|
356
|
+
|
|
357
|
+
logger.write("\n" + "=" * 90)
|
|
358
|
+
logger.write("BENCHMARK COMPLETED")
|
|
359
|
+
logger.write(f"Completed: {datetime.now()}")
|
|
360
|
+
logger.write("=" * 90)
|
|
361
|
+
print("=" * 75)
|
|
362
|
+
print(f"Full execution log saved to: {logger.log_file}")
|
|
363
|
+
|
|
364
|
+
if __name__ == "__main__":
|
|
365
|
+
try:
|
|
366
|
+
main()
|
|
367
|
+
except Exception as e:
|
|
368
|
+
if logger:
|
|
369
|
+
logger.write("\n" + "=" * 90)
|
|
370
|
+
logger.write("UNHANDLED EXCEPTION")
|
|
371
|
+
logger.write("=" * 90)
|
|
372
|
+
logger.write(str(e))
|
|
373
|
+
print(f"Benchmark failed: {e}")
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/trench-mark/__init__.py
|
|
4
|
+
src/trench-mark/cli.py
|
|
5
|
+
src/trench_mark.egg-info/PKG-INFO
|
|
6
|
+
src/trench_mark.egg-info/SOURCES.txt
|
|
7
|
+
src/trench_mark.egg-info/dependency_links.txt
|
|
8
|
+
src/trench_mark.egg-info/entry_points.txt
|
|
9
|
+
src/trench_mark.egg-info/requires.txt
|
|
10
|
+
src/trench_mark.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyyaml
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
trench-mark
|