Compare commits
2 Commits
a60a6c18b3
...
ef4852bdbf
| Author | SHA1 | Date | |
|---|---|---|---|
| ef4852bdbf | |||
| 49607c8a6a |
35
README.md
35
README.md
@@ -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"
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
74
mid_delay.py
74
mid_delay.py
@@ -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__':
|
||||||
|
|||||||
Reference in New Issue
Block a user