Compare commits

..

2 Commits

Author SHA1 Message Date
ef4852bdbf Add --list mode and update README
--list bypasses delay threshold and shows all MIDs sorted by start time
with Status column always visible. --filter / -f narrows by status:
done, aborted, pending, delayed.
2026-04-06 16:52:29 +08:00
49607c8a6a feat: start time for scanning 2026-04-06 14:25:09 +08:00
2 changed files with 92 additions and 17 deletions

View File

@@ -43,6 +43,8 @@ python3 mid_delay.py mail* # multiple files via shell glob
| `--no-partial` | | off | Exclude MIDs with no `Start MID` line in this log | | `--no-partial` | | off | Exclude MIDs with no `Start MID` line in this log |
| `--include-aborted` | | off | Include MIDs that were aborted (excluded by default) | | `--include-aborted` | | off | Include MIDs that were aborted (excluded by default) |
| `--start` | | (none) | Only include MIDs whose start time is on or after this datetime | | `--start` | | (none) | Only include MIDs whose start time is on or after this datetime |
| `--list` | | off | List all MIDs regardless of delay; use `--filter` to narrow by status |
| `--filter` | `-f` | (all) | Filter by status in `--list` mode: `done`, `aborted`, `pending`, `delayed` |
#### Status Categories (`--status`) #### Status Categories (`--status`)
@@ -64,6 +66,28 @@ python3 mid_delay.py mail* --start 2026-03-03
python3 mid_delay.py mail* --start "2026-03-03 14:30:00" python3 mid_delay.py mail* --start "2026-03-03 14:30:00"
``` ```
#### List Mode (`--list`)
Lists every tracked MID sorted by start time, bypassing the delay threshold entirely. The Status column is always shown. Use `--filter` / `-f` to narrow results by status.
| Status | Meaning |
|---|---|
| `done` | Delivered successfully, no retry event |
| `aborted` | Delivery aborted |
| `pending` | No finish event found in the log |
| `delayed` | Had at least one `Delayed:` retry event |
```bash
# List all MIDs
python3 mid_delay.py mail.current --list
# List only pending MIDs
python3 mid_delay.py mail* --list -f pending
# List delayed MIDs after a specific time
python3 mid_delay.py mail* --list -f delayed --start "2026-03-03 12:00:00"
```
#### Output Columns #### Output Columns
``` ```
@@ -74,7 +98,7 @@ MID Start End/Delayed Delay(m) Reason
- **Start** — Time of `Start MID` event. Marked with `*` if the original `Start MID` line was not found in the parsed log(s) and a `Delivery start` event was used as a fallback — delay figures for these may be unreliable. - **Start** — Time of `Start MID` event. Marked with `*` if the original `Start MID` line was not found in the parsed log(s) and a `Delivery start` event was used as a fallback — delay figures for these may be unreliable.
- **End/Delayed** — Time of `Message finished` event, or first `Delayed:` retry event if still pending - **End/Delayed** — Time of `Message finished` event, or first `Delayed:` retry event if still pending
- **Delay(m)** — Delay in minutes - **Delay(m)** — Delay in minutes
- **Status** — Only shown when `--status` is specified - **Status** — Always shown in `--list` mode; shown in delay mode when `--status` is specified
- **Reason** — First delay reason or abort reason found for the MID - **Reason** — First delay reason or abort reason found for the MID
#### Multi-File Parsing #### Multi-File Parsing
@@ -112,6 +136,15 @@ python3 mid_delay.py mail* --start "2026-03-03 12:00:00"
# Combine filters: slow delivered MIDs after noon, top 10 # Combine filters: slow delivered MIDs after noon, top 10
python3 mid_delay.py mail* -s slow_done --start "2026-03-03 12:00:00" -n 10 python3 mid_delay.py mail* -s slow_done --start "2026-03-03 12:00:00" -n 10
# List all MIDs sorted by start time
python3 mid_delay.py mail.current --list
# List only pending MIDs
python3 mid_delay.py mail* --list -f pending
# List delayed MIDs after a specific time
python3 mid_delay.py mail* --list -f delayed --start "2026-03-03 12:00:00"
``` ```
--- ---

View File

@@ -86,6 +86,17 @@ def parse_log(filepath, records=None):
return records return records
def simple_status(rec):
"""Return a plain status label regardless of delay."""
if rec['delayed_ts']:
return 'delayed'
if rec['end_status'] == 'done':
return 'done'
if rec['end_status'] == 'aborted':
return 'aborted'
return 'pending'
def classify(rec, threshold): def classify(rec, threshold):
start = rec['start_ts'] start = rec['start_ts']
end = rec['end_ts'] end = rec['end_ts']
@@ -196,9 +207,20 @@ def main():
help='Exclude MIDs with no start timestamp in this log', help='Exclude MIDs with no start timestamp in this log',
) )
parser.add_argument( parser.add_argument(
'--no-aborted', '--include-aborted',
action='store_true', action='store_true',
help='Exclude MIDs that were aborted', help='Include MIDs that were aborted (aborted MIDs are excluded by default)',
)
parser.add_argument(
'--list',
action='store_true',
help='List all MIDs regardless of delay; use --filter to narrow by status',
)
parser.add_argument(
'--filter', '-f',
choices=['done', 'aborted', 'pending', 'delayed'],
default=None,
help='Filter by status in --list mode: done, aborted, pending, delayed',
) )
parser.add_argument( parser.add_argument(
'--start', '--start',
@@ -231,24 +253,44 @@ def main():
for rec in records.values(): for rec in records.values():
if args.no_partial and rec['start_ts'] is None: if args.no_partial and rec['start_ts'] is None:
continue continue
entry = classify(rec, args.threshold) if not args.include_aborted and rec['end_status'] == 'aborted':
if entry is None:
continue
label, delay = entry
if args.status and args.status != 'all' and label != args.status:
continue
if args.pending and rec['end_ts'] is not None:
continue
if args.no_aborted and rec['end_status'] == 'aborted':
continue continue
if since_dt and (rec['start_ts'] is None or rec['start_ts'] < since_dt): if since_dt and (rec['start_ts'] is None or rec['start_ts'] < since_dt):
continue continue
reason = rec['delay_reason'] or rec['abort_reason'] or '' if args.pending and rec['end_ts'] is not None:
results.append((delay, rec['mid'], rec['start_ts'], rec['end_ts'], label, reason, rec['start_fallback'])) continue
results.sort(key=lambda x: x[0], reverse=True) if args.list:
show_status = args.status is not None label = simple_status(rec)
format_output(results, args.top, show_status) if args.filter and label != args.filter:
continue
start = rec['start_ts']
end = rec['end_ts'] or rec['delayed_ts']
if start and end:
delay = (end - start).total_seconds() / 60
else:
delay = 0.0
reason = rec['delay_reason'] or rec['abort_reason'] or ''
results.append((rec['start_ts'] or datetime.min, rec['mid'], rec['start_ts'], rec['end_ts'], label, reason, rec['start_fallback'], delay))
else:
entry = classify(rec, args.threshold)
if entry is None:
continue
label, delay = entry
if args.status and args.status != 'all' and label != args.status:
continue
reason = rec['delay_reason'] or rec['abort_reason'] or ''
results.append((delay, rec['mid'], rec['start_ts'], rec['end_ts'], label, reason, rec['start_fallback']))
if args.list:
results.sort(key=lambda x: x[0]) # sort by start time
# repack into format_output tuple shape: (delay, mid, start, end, label, reason, start_fallback)
results = [(r[7], r[1], r[2], r[3], r[4], r[5], r[6]) for r in results]
format_output(results, args.top, show_status=True)
else:
results.sort(key=lambda x: x[0], reverse=True)
show_status = args.status is not None
format_output(results, args.top, show_status)
if __name__ == '__main__': if __name__ == '__main__':