177 lines
6.4 KiB
Python
Executable File
177 lines
6.4 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
from __future__ import print_function
|
|
|
|
import argparse
|
|
import calendar
|
|
import csv
|
|
import datetime
|
|
import locale
|
|
import os
|
|
import sys
|
|
|
|
import pytz
|
|
import yaml
|
|
|
|
# Ensure calendar.day_name gives us Monday, Tuesday, ...
|
|
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
|
|
|
|
BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), ".."))
|
|
EAVESDROP = 'eavesdrop.openstack.org'
|
|
MEETINGS_PATH = os.path.join(BASE_DIR, 'meetings')
|
|
WEEKDAYS = list(calendar.day_name)
|
|
WEEK_COUNTS = {'first-thursday': 2, 'first-friday': 2, 'weekly': 2,
|
|
'biweekly-even': 1, 'biweekly-odd': 1,
|
|
'quadweekly': 1, 'quadweekly-alternate': 1, 'adhoc': 0}
|
|
CHANNELS = ['openstack-meeting', 'openstack-meeting-alt',
|
|
'openstack-meeting-3', 'openstack-meeting-4']
|
|
|
|
|
|
def main():
|
|
args = parse_args()
|
|
meetings = read_meetings(MEETINGS_PATH)
|
|
|
|
meeting_counts = calculate_meeting_counts(meetings)
|
|
|
|
print("Day\tUTC Hour")
|
|
available_slots = 2 * len(CHANNELS)
|
|
full_time_slot = available_slots - args.sensitivity
|
|
for day in WEEKDAYS:
|
|
for hour in range(24):
|
|
slot_usage = len(meeting_counts[hour][day])
|
|
if slot_usage >= full_time_slot:
|
|
print('{:<10} {}:00 {:<2} out of {} slots full'.format(
|
|
day, hour, slot_usage, available_slots))
|
|
# Handy for debugging
|
|
# print("\t{}".format(
|
|
# "\n\t".join(sorted(meeting_counts[hour][day]))))
|
|
slot_meetings = sorted(set(meeting_counts[hour][day]))
|
|
for meeting_info in slot_meetings:
|
|
print('{:<4}{}'.format('', meeting_info))
|
|
|
|
if args.csv:
|
|
print()
|
|
write_csv_file(args.csv, meeting_counts)
|
|
|
|
|
|
def calculate_meeting_counts(meetings):
|
|
now = datetime.datetime.utcnow().replace(tzinfo=pytz.utc)
|
|
meeting_counts = {}
|
|
for hour in range(24):
|
|
meeting_counts[hour] = {k: [] for k in WEEKDAYS}
|
|
|
|
for meeting in meetings:
|
|
if 'meeting_id' in meeting:
|
|
meeting_id = ('http://{}/meetings/{}/{:4d}/?C=N;O=D'.format(
|
|
EAVESDROP, meeting['meeting_id'].replace('-', '_'), now.year))
|
|
else:
|
|
meeting_id = meeting['filefrom']
|
|
|
|
for schedule in meeting['schedule']:
|
|
try:
|
|
day = schedule['day']
|
|
time = schedule['time']
|
|
frequency = schedule['frequency']
|
|
week_count = WEEK_COUNTS[frequency]
|
|
irc = schedule['irc']
|
|
except KeyError:
|
|
print("KeyError in here somewhere!")
|
|
print("meeting = {}".format(meeting))
|
|
print("schedule = {}".format(schedule))
|
|
print("\n")
|
|
continue
|
|
|
|
hour = int(time[:-2])
|
|
mins = int(time[-2:])
|
|
duration = int(schedule.get('duration', 60))
|
|
if duration > 60:
|
|
print("Meeting longer than 60 minutes. We don't understand "
|
|
"that yet")
|
|
print("meeting = {}".format(meeting))
|
|
print("schedule = {}".format(schedule))
|
|
print("\n")
|
|
|
|
if irc not in CHANNELS:
|
|
# Handy for debugging
|
|
# print("{}: {}".format(meeting['filefrom'], schedule))
|
|
continue
|
|
|
|
meeting_info = (
|
|
"{:<13} - {}/{} - {:<21} - {}".format(
|
|
frequency, time, duration, irc, meeting_id))
|
|
|
|
# This is a little hacky way to handle alternating meetings. The
|
|
# "counts" gathered are per fortnight so a weekly meeting takes up
|
|
# 2 slots, and an alternating (biweekly-*) only one. This means
|
|
# that a "full slot" will be one that has 8 (2 * number of meeting
|
|
# channels) scheduled meetings
|
|
for i in range(week_count):
|
|
meeting_counts[hour][day].append(meeting_info)
|
|
# Check for and record meetings that cross multiple hours (This
|
|
# assumes that we don't have any meetings that are longer than
|
|
# 60mins)
|
|
if (mins + duration) > 60:
|
|
meeting_counts[(hour+1) % 24][day].append(meeting_info)
|
|
return meeting_counts
|
|
|
|
|
|
def write_csv_file(filename, meeting_counts):
|
|
filename = os.path.abspath(os.path.expanduser(filename))
|
|
with open(filename, 'w') as out_file:
|
|
writer = csv.writer(out_file)
|
|
writer.writerow(["Hour"] + WEEKDAYS)
|
|
for hour in range(24):
|
|
row = [hour] + [len(meeting_counts[hour][day]) for day in WEEKDAYS]
|
|
writer.writerow(row)
|
|
print("Created CSV file of meeting slot usage at: {}".format(filename))
|
|
|
|
|
|
def read_meetings(meeting_directory):
|
|
meetings = []
|
|
if not os.path.isdir(meeting_directory):
|
|
sys.exit("Unable to find meeting directory: {}".format(
|
|
meeting_directory))
|
|
|
|
for dirpath, dirnames, filenames in os.walk(meeting_directory):
|
|
for file_name in filenames:
|
|
if not file_name.endswith('.yaml'):
|
|
continue
|
|
full_path = os.path.join(dirpath, file_name)
|
|
with open(full_path, 'r') as f_obj:
|
|
obj = yaml.safe_load(f_obj)
|
|
obj['filefrom'] = full_path
|
|
meetings.append(obj)
|
|
return meetings
|
|
|
|
|
|
def parse_args():
|
|
parser = argparse.ArgumentParser(
|
|
description='Check meeting count time usage')
|
|
parser.add_argument(
|
|
'--csv', metavar='FILE_NAME',
|
|
help='If specified, write counts to the specified CSV file')
|
|
parser.add_argument(
|
|
'--sensitivity', type=int, default=1,
|
|
help='Sensitivity of reporting. '
|
|
'Defaults to 1, which means report if no weekly slot is '
|
|
'available at the time slots considered.')
|
|
|
|
args = parser.parse_args()
|
|
return args
|
|
|
|
|
|
if '__main__' == __name__:
|
|
sys.exit(main())
|