Skip to content

Commit 6b55414

Browse files
author
Scott Collins
committed
Added unprocessed category to summary table, and flattened table to remove grouping by batch index
1 parent f26ac60 commit 6b55414

3 files changed

Lines changed: 163 additions & 50 deletions

File tree

src/pds/ingress/client/pds_ingress_client.py

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
from pds.ingress.util.report_util import parts_to_xml
4747
from pds.ingress.util.report_util import print_ingress_summary
4848
from pds.ingress.util.report_util import read_manifest_file
49+
from pds.ingress.util.report_util import update_summary_table
4950
from pds.ingress.util.report_util import write_manifest_file
5051
from requests.exceptions import RequestException
5152
from tqdm.utils import CallbackIOWrapper
@@ -165,6 +166,8 @@ def _process_batch(batch_index, request_batch, node_id, force_overwrite, api_gat
165166
fully processed.
166167
167168
"""
169+
global SUMMARY_TABLE # noqa: F824
170+
168171
logger = get_logger("_process_batch", console=False)
169172

170173
# Get an avaialble Batch progress bar to update while iterating through this
@@ -191,7 +194,8 @@ def _process_batch(batch_index, request_batch, node_id, force_overwrite, api_gat
191194
except RequestException as err:
192195
# If here, the HTTP request error was unrecoverable by a backoff/retry
193196
trimmed_path = ingress_response.get("trimmed_path")
194-
SUMMARY_TABLE["failed"][batch_index].add(trimmed_path)
197+
ingress_path = ingress_response.get("ingress_path")
198+
update_summary_table(SUMMARY_TABLE, "failed", ingress_path)
195199

196200
logger.error(
197201
"Batch %d : Ingress failed for %s, HTTP code: %s\n HTTP response text:\n%s",
@@ -455,18 +459,19 @@ def ingress_file_to_s3(ingress_response, batch_index, batch_pbar):
455459
If an unexpected response is received from the Ingress Lambda app.
456460
457461
"""
462+
global SUMMARY_TABLE # noqa: F824
463+
458464
logger = get_logger("ingress_file_to_s3", console=False)
459465

460466
response_result = int(ingress_response.get("result", -1))
461467
trimmed_path = ingress_response.get("trimmed_path")
468+
ingress_path = ingress_response.get("ingress_path")
462469

463470
if response_result == HTTPStatus.OK:
464471
s3_ingress_url = ingress_response.get("s3_url")
465472

466473
logger.info("Batch %d : Ingesting %s to %s", batch_index, trimmed_path, s3_ingress_url.split("?")[0])
467474

468-
ingress_path = ingress_response.get("ingress_path")
469-
470475
if not ingress_path:
471476
raise ValueError("No ingress path provided with response for %s", trimmed_path)
472477

@@ -496,25 +501,24 @@ def ingress_file_to_s3(ingress_response, batch_index, batch_pbar):
496501
response.raise_for_status()
497502

498503
logger.info("Batch %d : %s Ingest complete", batch_index, trimmed_path)
499-
SUMMARY_TABLE["uploaded"][batch_index].add(trimmed_path)
500-
501-
# Update total number of bytes transferrred
502-
SUMMARY_TABLE["transferred"] += os.stat(ingress_path).st_size
504+
update_summary_table(SUMMARY_TABLE, "uploaded", ingress_path)
505+
upload_pbar.reset()
503506
elif response_result == HTTPStatus.NO_CONTENT:
504507
logger.info(
505508
"Batch %d : Skipping ingress for %s, reason %s", batch_index, trimmed_path, ingress_response.get("message")
506509
)
507-
SUMMARY_TABLE["skipped"][batch_index].add(trimmed_path)
510+
update_summary_table(SUMMARY_TABLE, "skipped", ingress_path)
508511
elif response_result == HTTPStatus.NOT_FOUND:
509512
logger.warning(
510513
"Batch %d : Ingress failed for %s, reason: %s", batch_index, trimmed_path, ingress_response.get("message")
511514
)
512-
SUMMARY_TABLE["failed"][batch_index].add(trimmed_path)
515+
update_summary_table(SUMMARY_TABLE, "failed", ingress_path)
513516
else:
514517
logger.error("Batch %d : Unexepected response code (%d) from Ingress service", batch_index, response_result)
515518
raise RuntimeError
516519

517520

521+
# noinspection PyUnreachableCode
518522
@backoff.on_exception(
519523
backoff.expo,
520524
Exception,
@@ -545,15 +549,17 @@ def ingress_multipart_file_to_s3(ingress_response, batch_index, batch_pbar):
545549
If an unexpected response is received from the Ingress Lambda app.
546550
547551
"""
552+
global SUMMARY_TABLE # noqa: F824
553+
548554
logger = get_logger("ingress_multipart_file_to_s3", console=False)
549555

550556
response_result = int(ingress_response.get("result", -1))
551557
trimmed_path = ingress_response.get("trimmed_path")
558+
ingress_path = ingress_response.get("ingress_path")
552559

553560
if response_result == HTTPStatus.OK:
554561
logger.info("Batch %d : Performing Multipart Upload for %s", batch_index, trimmed_path)
555562

556-
ingress_path = ingress_response.get("ingress_path")
557563
s3_ingress_urls = ingress_response.get("s3_urls", [])
558564
upload_complete_url = ingress_response.get("upload_complete_url")
559565
upload_abort_url = ingress_response.get("upload_abort_url")
@@ -601,20 +607,17 @@ def ingress_multipart_file_to_s3(ingress_response, batch_index, batch_pbar):
601607
response.raise_for_status()
602608

603609
logger.info("Batch %d : %s Multipart Upload complete", batch_index, trimmed_path)
604-
SUMMARY_TABLE["uploaded"][batch_index].add(trimmed_path)
605-
606-
# Update total number of bytes transferrred
607-
SUMMARY_TABLE["transferred"] += os.stat(ingress_path).st_size
610+
update_summary_table(SUMMARY_TABLE, "uploaded", ingress_path)
608611
elif response_result == HTTPStatus.NO_CONTENT:
609612
logger.info(
610613
"Batch %d : Skipping ingress for %s, reason %s", batch_index, trimmed_path, ingress_response.get("message")
611614
)
612-
SUMMARY_TABLE["skipped"][batch_index].add(trimmed_path)
615+
update_summary_table(SUMMARY_TABLE, "skipped", ingress_path)
613616
elif response_result == HTTPStatus.NOT_FOUND:
614617
logger.warning(
615618
"Batch %d : Ingress failed for %s, reason: %s", batch_index, trimmed_path, ingress_response.get("message")
616619
)
617-
SUMMARY_TABLE["failed"][batch_index].add(trimmed_path)
620+
update_summary_table(SUMMARY_TABLE, "failed", ingress_path)
618621
else:
619622
logger.error("Batch %d : Unexepected response code (%d) from Ingress service", batch_index, response_result)
620623
raise RuntimeError
@@ -789,17 +792,24 @@ def main(args):
789792
with get_path_progress_bar(args.ingress_paths) as pbar:
790793
resolved_ingress_paths = PathUtil.resolve_ingress_paths(args.ingress_paths, pbar)
791794

795+
# Initialize the summary table, and populate the "unprocessed" table the set
796+
# of resolved ingress paths
797+
SUMMARY_TABLE = initialize_summary_table()
798+
update_summary_table(SUMMARY_TABLE, "unprocessed", resolved_ingress_paths)
799+
792800
node_id = args.node
793801

794802
# Set the joblib pool size based on the number of "threads" requested
795803
PARALLEL.n_jobs = args.num_threads
796804

797805
# Break the set of ingress paths into batches based on configured size
798806
batch_size = int(config["OTHER"].get("batch_size", fallback=1))
807+
SUMMARY_TABLE["batch_size"] = batch_size
799808

800809
batched_ingress_paths = list(batched(resolved_ingress_paths, batch_size))
801810
logger.info("Using batch size of %d", batch_size)
802811
logger.info("Request (%d files) split into %d batches", len(resolved_ingress_paths), len(batched_ingress_paths))
812+
SUMMARY_TABLE["num_batches"] = len(batched_ingress_paths)
803813

804814
if args.manifest_path and os.path.exists(args.manifest_path):
805815
logger.info("Reading existing manifest file %s", args.manifest_path)
@@ -813,8 +823,6 @@ def main(args):
813823
write_manifest_file(MANIFEST, os.path.abspath(args.manifest_path))
814824

815825
if not args.dry_run:
816-
SUMMARY_TABLE = initialize_summary_table()
817-
818826
cognito_config = config["COGNITO"]
819827

820828
# TODO: add support for command-line username/password?
@@ -848,23 +856,21 @@ def main(args):
848856
finally:
849857
close_batch_progress_bars()
850858

851-
# Capture completion time of transfer and batch configuration
852-
SUMMARY_TABLE["end_time"] = time.time()
853-
SUMMARY_TABLE["batch_size"] = batch_size
854-
SUMMARY_TABLE["num_batches"] = len(batched_ingress_paths)
855-
856-
# Create the JSON report file, if requested
857-
if args.report_path:
858-
create_report_file(args, SUMMARY_TABLE)
859-
860-
# Print the summary table
861-
print_ingress_summary(SUMMARY_TABLE)
862-
863859
# Flush all logged statements to CloudWatch Logs
864860
log_util.CLOUDWATCH_HANDLER.flush()
865861
else:
866862
logger.info("Dry run requested, skipping ingress request submission.")
867863

864+
# Capture completion time
865+
SUMMARY_TABLE["end_time"] = time.time()
866+
867+
# Create the JSON report file, if requested
868+
if args.report_path:
869+
create_report_file(args, SUMMARY_TABLE)
870+
871+
# Print the summary table
872+
print_ingress_summary(SUMMARY_TABLE)
873+
868874

869875
def console_main():
870876
"""No argument entrypoint for use with setuptools"""

src/pds/ingress/util/report_util.py

Lines changed: 64 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,28 @@
88
99
"""
1010
import json
11+
import multiprocessing
12+
import os
1113
import time
12-
from collections import defaultdict
1314
from datetime import datetime
1415
from datetime import timezone
15-
from itertools import chain
1616

1717
from pds.ingress.util.log_util import get_logger
1818

19+
REPORT_SEMAPHORE = multiprocessing.Semaphore(1)
20+
"""Semaphore used to control write access to Batch progress bars"""
21+
1922
EXPECTED_MANIFEST_KEYS = ("ingress_path", "md5", "size", "last_modified")
2023
"""The keys we expect to find assigned to each mapping within a read manifest"""
2124

2225

2326
def initialize_summary_table():
2427
"""Returns a summary table initialized to its default state."""
2528
return {
26-
"uploaded": defaultdict(set),
27-
"skipped": defaultdict(set),
28-
"failed": defaultdict(set),
29+
"uploaded": set(),
30+
"skipped": set(),
31+
"failed": set(),
32+
"unprocessed": set(),
2933
"transferred": 0,
3034
"start_time": time.time(),
3135
"end_time": None,
@@ -34,6 +38,46 @@ def initialize_summary_table():
3438
}
3539

3640

41+
def update_summary_table(summary_table, key, paths):
42+
"""
43+
Updates the summary table with the provided key, index, and value.
44+
45+
Parameters
46+
----------
47+
summary_table : dict
48+
The summary table to update.
49+
key : str
50+
The key in the summary table to update (e.g., "uploaded", "skipped", "failed").
51+
paths : str or list of str
52+
The path value (or values) to add to the summary table for the specified key.
53+
Note, these paths should be the absolute paths to files that were processed,
54+
not the "trimmed" relative paths.
55+
56+
"""
57+
if key not in ("uploaded", "skipped", "failed", "unprocessed"):
58+
raise KeyError(f"Invalid key '{key}' provided for summary table update.")
59+
60+
if key not in summary_table:
61+
raise KeyError(f"Key '{key}' not found in summary table.")
62+
63+
if not isinstance(paths, list):
64+
paths = [paths]
65+
66+
with REPORT_SEMAPHORE:
67+
summary_table[key].update(paths)
68+
69+
if key == "uploaded":
70+
# Update total number of bytes transferrred for successful uploads
71+
summary_table["transferred"] += sum(os.stat(path).st_size for path in paths)
72+
73+
# If this file or files previous failed, remove from the failed set
74+
summary_table["failed"] -= set(paths)
75+
76+
# Prune any now-visted paths from the unprocessed set
77+
if key != "unprocessed":
78+
summary_table["unprocessed"] -= set(paths)
79+
80+
3781
def print_ingress_summary(summary_table):
3882
"""
3983
Prints the summary report for last execution of the client script.
@@ -46,9 +90,10 @@ def print_ingress_summary(summary_table):
4690
"""
4791
logger = get_logger("print_ingress_summary")
4892

49-
num_uploaded = sum(len(batch) for batch in summary_table["uploaded"].values())
50-
num_skipped = sum(len(batch) for batch in summary_table["skipped"].values())
51-
num_failed = sum(len(batch) for batch in summary_table["failed"].values())
93+
num_uploaded = len(summary_table["uploaded"])
94+
num_skipped = len(summary_table["skipped"])
95+
num_failed = len(summary_table["failed"])
96+
num_unprocessed = len(summary_table["unprocessed"])
5297
start_time = summary_table["start_time"]
5398
end_time = summary_table["end_time"]
5499
transferred = summary_table["transferred"]
@@ -61,7 +106,8 @@ def print_ingress_summary(summary_table):
61106
logger.info("Uploaded: %d file(s)", num_uploaded)
62107
logger.info("Skipped: %d file(s)", num_skipped)
63108
logger.info("Failed: %d file(s)", num_failed)
64-
logger.info("Total: %d files(s)", num_uploaded + num_skipped + num_failed)
109+
logger.info("Unprocessed: %d file(s)", num_unprocessed)
110+
logger.info("Total: %d files(s)", num_uploaded + num_skipped + num_failed + num_unprocessed)
65111
logger.info("Time elapsed: %.2f seconds", end_time - start_time)
66112
logger.info("Bytes tranferred: %d", transferred)
67113

@@ -147,9 +193,10 @@ def create_report_file(args, summary_table):
147193
"""
148194
logger = get_logger("create_report_file")
149195

150-
uploaded = list(sorted(chain(*summary_table["uploaded"].values())))
151-
skipped = list(sorted(chain(*summary_table["skipped"].values())))
152-
failed = list(sorted(chain(*summary_table["failed"].values())))
196+
uploaded = list(sorted(summary_table["uploaded"]))
197+
skipped = list(sorted(summary_table["skipped"]))
198+
failed = list(sorted(summary_table["failed"]))
199+
unprocessed = list(sorted(summary_table["unprocessed"]))
153200

154201
report = {
155202
"Arguments": str(args),
@@ -163,10 +210,14 @@ def create_report_file(args, summary_table):
163210
"Total Skipped": len(skipped),
164211
"Failed": failed,
165212
"Total Failed": len(failed),
213+
"Unprocessed": unprocessed,
214+
"Total Unprocessed": len(unprocessed),
166215
"Bytes Transferred": summary_table["transferred"],
167216
}
168217

169-
report["Total Files"] = report["Total Uploaded"] + report["Total Skipped"] + report["Total Failed"]
218+
report["Total Files"] = (
219+
report["Total Uploaded"] + report["Total Skipped"] + report["Total Failed"] + report["Total Unprocessed"]
220+
)
170221

171222
try:
172223
logger.info("Writing JSON summary report to %s", args.report_path)

0 commit comments

Comments
 (0)