Subj : BinkD-Stats 2.0 - BinkD Log Analyzer
To : All
From : Stephen Walsh
Date : Mon Oct 06 2025 01:59 pm
Hello everybody!
# BinkD-Stats 2.0
Python rewrite of the BinkD log analyzer with significant improvements.
## Improvements Over Perl Version
### New Features
- **JSON Export** - Export statistics to JSON format for integration with
other tools
- **CSV Export** - Export statistics to CSV for spreadsheet analysis
- **Better Date Handling** - Uses Python's `datetime` module for more
accurate date parsing
- **File Count Tracking** - Tracks sent/recv file counts in addition to bytes
- **Improved Error Handling** - Better error messages and exception handling
- **Modern CLI** - Uses argparse for better help and argument parsing
### Technical Improvements
- **More Accurate CPS Calculations** - Properly averages CPS from actual
transfer speeds
- **Cleaner Code** - Object-oriented design with clear separation of concerns
- **Better Type Safety** - Type hints for better code documentation
- **Cross-platform** - Works on Linux, macOS, and Windows
### Compatibility
- Produces identical statistics to the Perl version
- Same command-line interface (with enhancements)
- Backward compatible with existing usage
## Usage
### Basic Usage
```bash
# Show all statistics
python3 binkd-stats.py -f /var/log/binkd.log
# Show last 7 days
python3 binkd-stats.py -f /var/log/binkd.log -d 7
# Show last 30 days
python3 binkd-stats.py -f /var/log/binkd.log -d 30
```
# Both text and JSON/CSV output
python3 binkd-stats.py -f /var/log/binkd.log --json stats.json --csv stats.csv
```
### Help
```bash
python3 binkd-stats.py --help
```
## Output Format
### Text Report (Console)
```
BinkD Connection's
Statistics For Last 7 day's, Created Mon Oct 6 12:58:59 2025
-----------------------------------------------------------------------------
Address Sessions Sent Received CPS In CPS Out
-----------------------------------------------------------------------------
1:9/36 15 97.0k 0 0.00 8744.85
3:633/10 345 3.2k 8.0M 22171.96 650.00
-----------------------------------------------------------------------------
Total Received : 31.3M Total Sessions : 10596
Total Sent : 58.2M Average CPS In : 12052.24
Total Traffic : 89.4M Average CPS Out : 6507.43
-----------------------------------------------------------------------------
```
def parse_date(self, line: str) -> Optional[datetime]:
"""Parse date from log line (format: DD MMM HH:MM:SS)"""
match = re.match(r'[+\-!? ]\s*(\d+)\s+(\w+)\s+(\d+):(\d+):(\d+)', line)
if not match:
return None
day, month_name, hour, minute, second = match.groups()
month = self.months.get(month_name)
if not month:
return None
# Determine year (assume current year unless date is in future)
current_year = datetime.now().year
try:
dt = datetime(current_year, month, int(day),
int(hour), int(minute), int(second))
# If date is in future, it must be from previous year
if dt > datetime.now():
dt = datetime(current_year - 1, month, int(day),
int(hour), int(minute), int(second))
return dt
except ValueError:
return None
def is_date_valid(self, dt: Optional[datetime]) -> bool:
"""Check if date is within the requested time range"""
if dt is None:
return False
if self.days == 0:
return True
cutoff = datetime.now() - timedelta(days=self.days)
return dt >= cutoff
with open(self.log_file, 'r', encoding='utf-8', errors='replace') as f:
for line in f:
line = line.strip()
# Parse date
dt = self.parse_date(line)
if dt:
if self.start_date is None or dt < self.start_date:
self.start_date = dt
if self.end_date is None or dt > self.end_date:
self.end_date = dt
# Check if line is within date range
if not self.is_date_valid(dt):
continue
# Parse "done" lines for session summary
match = done_pattern.search(line)
if match:
addr = match.group(1)
sent_files = int(match.group(2))
recv_files = int(match.group(3))
sent_bytes = int(match.group(4))
recv_bytes = int(match.group(5))
# Parse "rcvd" lines for CPS
match = rcvd_pattern.search(line)
if match:
cps = float(match.group(2))
addr = match.group(3)
self.stats[addr]['recv_cps'].append(cps)
# Parse "sent" lines for CPS
match = sent_pattern.search(line)
if match:
cps = float(match.group(2))
addr = match.group(3)
self.stats[addr]['send_cps'].append(cps)
def calculate_avg_cps(self, cps_list: List[float]) -> float:
"""Calculate average CPS from list"""
return sum(cps_list) / len(cps_list) if cps_list else 0.0
def print_text_report(self):
"""Print statistics report to console"""
if not self.stats:
print("\n[There Are No Connections To Report]\n")
return
print()
print(" BinkD Connection's")
print()
if self.days == 0:
if self.start_date:
start_str = self.start_date.strftime("%c")
else:
start_str = "Unknown"
if self.end_date:
end_str = self.end_date.strftime("%c")
else:
end_str = "Unknown"
stat_line = f"Statistics From {start_str} Thru To {end_str}"
# Center the line within 78 characters
print(stat_line.center(78))
else:
now_str = datetime.now().strftime("%c")
print(f" Statistics For Last {self.days} day's, "
f"Created {now_str}")