Skip to content

Commit d64307b

Browse files
committed
Add progress logging and ETA for all matcher pipes #688
Signed-off-by: Thomas Druez <tdruez@nexb.com>
1 parent 33717e2 commit d64307b

3 files changed

Lines changed: 123 additions & 59 deletions

File tree

scanpipe/pipelines/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@
2222

2323
import inspect
2424
import logging
25-
import timeit
2625
import traceback
2726
import warnings
2827
from contextlib import contextmanager
2928
from functools import wraps
3029
from pydoc import getdoc
3130
from pydoc import splitdoc
31+
from timeit import default_timer as timer
3232

3333
from django.utils import timezone
3434

@@ -113,7 +113,7 @@ def execute(self):
113113
self.run.current_step = f"{current_index}/{steps_count} {step_name}"[:256]
114114

115115
self.log(f"Step [{step_name}] starting")
116-
start_time = timeit.default_timer()
116+
start_time = timer()
117117

118118
try:
119119
step(self)
@@ -122,7 +122,7 @@ def execute(self):
122122
tb = "".join(traceback.format_tb(e.__traceback__))
123123
return 1, f"{e}\n\nTraceback:\n{tb}"
124124

125-
run_time = timeit.default_timer() - start_time
125+
run_time = timer() - start_time
126126
self.log(f"Step [{step.__name__}] completed in {run_time:.2f} seconds")
127127

128128
self.run.current_step = ""

scanpipe/pipes/__init__.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from datetime import datetime
2828
from pathlib import Path
2929
from time import sleep
30+
from timeit import default_timer as timer
3031

3132
from django.db.models import Count
3233

@@ -302,7 +303,14 @@ def get_progress_percentage(current_index, total_count):
302303
return progress
303304

304305

305-
def log_progress(log_func, current_index, total_count, last_percent, increment_percent):
306+
def log_progress(
307+
log_func,
308+
current_index,
309+
total_count,
310+
last_percent,
311+
increment_percent,
312+
start_time=None,
313+
):
306314
"""
307315
Log progress updates every `increment_percent` percentage points, given the
308316
current index and total count of objects.
@@ -311,7 +319,14 @@ def log_progress(log_func, current_index, total_count, last_percent, increment_p
311319
progress_percentage = int(get_progress_percentage(current_index, total_count))
312320
if progress_percentage >= last_percent + increment_percent:
313321
last_percent = progress_percentage
314-
log_func(
315-
f"Progress: {progress_percentage}% ({current_index:,d}/{total_count:,d})"
316-
)
322+
msg = f"Progress: {progress_percentage}% ({current_index:,d}/{total_count:,d})"
323+
324+
if start_time:
325+
run_time = timer() - start_time
326+
eta = round(run_time / progress_percentage * (100 - progress_percentage))
327+
if eta:
328+
msg += f" ETA: {round(eta)} seconds"
329+
330+
log_func(msg)
331+
317332
return last_percent

scanpipe/pipes/d2d.py

Lines changed: 101 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import difflib
2424
from pathlib import Path
25+
from timeit import default_timer as timer
2526

2627
from scanpipe import pipes
2728
from scanpipe.models import CodebaseRelation
@@ -74,66 +75,93 @@ def get_best_checksum_matches(to_resource, matches):
7475
return matches
7576

7677

78+
def _resource_checksum_match(to_resource, from_resources, checksum_field):
79+
checksum_value = getattr(to_resource, checksum_field)
80+
matches = from_resources.filter(**{checksum_field: checksum_value})
81+
for match in get_best_checksum_matches(to_resource, matches):
82+
pipes.make_relationship(
83+
from_resource=match,
84+
to_resource=to_resource,
85+
relationship=CodebaseRelation.Relationship.IDENTICAL,
86+
match_type=checksum_field,
87+
)
88+
89+
7790
def checksum_match(project, checksum_field, logger=None):
7891
"""Match using checksum."""
7992
project_files = project.codebaseresources.files().no_status()
8093
from_resources = project_files.from_codebase().has_value(checksum_field)
8194
to_resources = (
8295
project_files.to_codebase().has_value(checksum_field).has_no_relation()
8396
)
97+
resource_count = to_resources.count()
8498

8599
if logger:
86-
resource_count = to_resources.count()
87100
logger(
88101
f"Matching {resource_count:,d} to/ resources using {checksum_field} "
89102
f"against from/ codebase"
90103
)
91104

92-
for to_resource in to_resources:
93-
checksum_value = getattr(to_resource, checksum_field)
94-
matches = from_resources.filter(**{checksum_field: checksum_value})
95-
for match in get_best_checksum_matches(to_resource, matches):
96-
pipes.make_relationship(
97-
from_resource=match,
98-
to_resource=to_resource,
99-
relationship=CodebaseRelation.Relationship.IDENTICAL,
100-
match_type=checksum_field,
101-
)
105+
resource_iterator = to_resources.iterator(chunk_size=2000)
106+
last_percent = 0
107+
start_time = timer()
108+
for resource_index, to_resource in enumerate(resource_iterator):
109+
last_percent = pipes.log_progress(
110+
logger,
111+
resource_index,
112+
resource_count,
113+
last_percent,
114+
increment_percent=10,
115+
start_time=start_time,
116+
)
117+
_resource_checksum_match(to_resource, from_resources, checksum_field)
118+
119+
120+
def _resource_java_to_class_match(to_resource, from_resources):
121+
qualified_class = get_extracted_subpath(to_resource.path)
122+
123+
if "$" in to_resource.name: # inner class
124+
path_parts = Path(qualified_class.lstrip("/")).parts
125+
parts_without_name = list(path_parts[:-1])
126+
from_name = to_resource.name.split("$")[0] + ".java"
127+
qualified_java = "/".join(parts_without_name + [from_name])
128+
else:
129+
qualified_java = qualified_class.replace(".class", ".java")
130+
131+
matches = from_resources.filter(path__endswith=qualified_java)
132+
for match in matches:
133+
pipes.make_relationship(
134+
from_resource=match,
135+
to_resource=to_resource,
136+
relationship=CodebaseRelation.Relationship.COMPILED,
137+
match_type="java_to_class",
138+
)
102139

103140

104141
def java_to_class_match(project, logger=None):
105142
"""Match a .java source to its compiled .class using fully qualified name."""
106-
from_extension = ".java"
107-
to_extension = ".class"
108-
109143
project_files = project.codebaseresources.files().no_status()
110144
from_resources = project_files.from_codebase()
111145
to_resources = project_files.to_codebase().has_no_relation()
112146

113-
to_resources_dot_class = to_resources.filter(name__endswith=to_extension)
147+
to_resources_dot_class = to_resources.filter(name__endswith=".class")
148+
resource_count = to_resources_dot_class.count()
114149
if logger:
115-
count = to_resources_dot_class.count()
116-
logger(f"Matching {count:,d} .class resources to .java")
150+
logger(f"Matching {resource_count:,d} .class resources to .java")
117151

118-
for to_resource in to_resources_dot_class:
119-
qualified_class = get_extracted_subpath(to_resource.path)
120-
121-
if "$" in to_resource.name: # inner class
122-
path_parts = Path(qualified_class.lstrip("/")).parts
123-
parts_without_name = list(path_parts[:-1])
124-
from_name = to_resource.name.split("$")[0] + from_extension
125-
qualified_java = "/".join(parts_without_name + [from_name])
126-
else:
127-
qualified_java = qualified_class.replace(to_extension, from_extension)
128-
129-
matches = from_resources.filter(path__endswith=qualified_java)
130-
for match in matches:
131-
pipes.make_relationship(
132-
from_resource=match,
133-
to_resource=to_resource,
134-
relationship=CodebaseRelation.Relationship.COMPILED,
135-
match_type="java_to_class",
136-
)
152+
resource_iterator = to_resources_dot_class.iterator(chunk_size=2000)
153+
last_percent = 0
154+
start_time = timer()
155+
for resource_index, to_resource in enumerate(resource_iterator):
156+
last_percent = pipes.log_progress(
157+
logger,
158+
resource_index,
159+
resource_count,
160+
last_percent,
161+
increment_percent=10,
162+
start_time=start_time,
163+
)
164+
_resource_java_to_class_match(to_resource, from_resources)
137165

138166

139167
def get_diff_ratio(to_resource, from_resource):
@@ -208,13 +236,35 @@ def path_match(project, logger=None):
208236

209237
resource_iterator = to_resources.iterator(chunk_size=2000)
210238
last_percent = 0
239+
start_time = timer()
211240
for resource_index, to_resource in enumerate(resource_iterator):
212241
last_percent = pipes.log_progress(
213-
logger, resource_index, resource_count, last_percent, increment_percent=5
242+
logger,
243+
resource_index,
244+
resource_count,
245+
last_percent,
246+
increment_percent=10,
247+
start_time=start_time,
214248
)
215249
_resource_path_match(to_resource, from_resources)
216250

217251

252+
def _resource_purldb_match(project, resource):
253+
if results := purldb.match_by_sha1(sha1=resource.sha1):
254+
package_data = results[0]
255+
package_data.pop("dependencies")
256+
package = pipes.update_or_create_package(
257+
project=project,
258+
package_data=package_data,
259+
codebase_resource=resource,
260+
)
261+
extracted_resources = project.codebaseresources.to_codebase().filter(
262+
path__startswith=f"{resource.path}-extract"
263+
)
264+
package.add_resources(extracted_resources)
265+
extracted_resources.update(status="application-package")
266+
267+
218268
def purldb_match(project, extensions, logger=None):
219269
to_resources = (
220270
project.codebaseresources.files()
@@ -223,25 +273,24 @@ def purldb_match(project, extensions, logger=None):
223273
.has_value("sha1")
224274
.filter(extension__in=extensions)
225275
)
276+
resource_count = to_resources.count()
226277

227278
if logger:
228-
resource_count = to_resources.count()
229279
extensions_str = ", ".join(extensions)
230280
logger(
231281
f"Matching {resource_count:,d} {extensions_str} resources against PurlDB"
232282
)
233283

234-
for resource in to_resources:
235-
if results := purldb.match_by_sha1(sha1=resource.sha1):
236-
package_data = results[0]
237-
package_data.pop("dependencies")
238-
package = pipes.update_or_create_package(
239-
project=project,
240-
package_data=package_data,
241-
codebase_resource=resource,
242-
)
243-
extracted_resources = project.codebaseresources.to_codebase().filter(
244-
path__startswith=f"{resource.path}-extract"
245-
)
246-
package.add_resources(extracted_resources)
247-
extracted_resources.update(status="application-package")
284+
resource_iterator = to_resources.iterator(chunk_size=2000)
285+
last_percent = 0
286+
start_time = timer()
287+
for resource_index, to_resource in enumerate(resource_iterator):
288+
last_percent = pipes.log_progress(
289+
logger,
290+
resource_index,
291+
resource_count,
292+
last_percent,
293+
increment_percent=10,
294+
start_time=start_time,
295+
)
296+
_resource_purldb_match(project, to_resource)

0 commit comments

Comments
 (0)