OpenStack IRC meetings schedule
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

177 lines
6.3 KiB

  1. #!/usr/bin/env python
  2. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  3. # not use this file except in compliance with the License. You may obtain
  4. # a copy of the License at
  5. #
  6. # http://www.apache.org/licenses/LICENSE-2.0
  7. #
  8. # Unless required by applicable law or agreed to in writing, software
  9. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  10. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  11. # License for the specific language governing permissions and limitations
  12. # under the License.
  13. from __future__ import print_function
  14. import argparse
  15. import calendar
  16. import csv
  17. import datetime
  18. import locale
  19. import os
  20. import sys
  21. import pytz
  22. import yaml
  23. # Ensure calendar.day_name gives us Monday, Tuesday, ...
  24. locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
  25. BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), ".."))
  26. EAVESDROP = 'eavesdrop.openstack.org'
  27. MEETINGS_PATH = os.path.join(BASE_DIR, 'meetings')
  28. WEEKDAYS = list(calendar.day_name)
  29. WEEK_COUNTS = {'first-thursday': 2, 'first-friday': 2, 'weekly': 2,
  30. 'biweekly-even': 1, 'biweekly-odd': 1,
  31. 'quadweekly': 1, 'quadweekly-alternate': 1, 'adhoc': 0}
  32. CHANNELS = ['openstack-meeting', 'openstack-meeting-alt',
  33. 'openstack-meeting-3']
  34. def main():
  35. args = parse_args()
  36. meetings = read_meetings(MEETINGS_PATH)
  37. meeting_counts = calculate_meeting_counts(meetings)
  38. print("Day\tUTC Hour")
  39. available_slots = 2 * len(CHANNELS)
  40. full_time_slot = available_slots - args.sensitivity
  41. for day in WEEKDAYS:
  42. for hour in range(24):
  43. slot_usage = len(meeting_counts[hour][day])
  44. if slot_usage >= full_time_slot:
  45. print('{:<10} {}:00 {:<2} out of {} slots full'.format(
  46. day, hour, slot_usage, available_slots))
  47. # Handy for debugging
  48. # print("\t{}".format(
  49. # "\n\t".join(sorted(meeting_counts[hour][day]))))
  50. slot_meetings = sorted(set(meeting_counts[hour][day]))
  51. for meeting_info in slot_meetings:
  52. print('{:<4}{}'.format('', meeting_info))
  53. if args.csv:
  54. print()
  55. write_csv_file(args.csv, meeting_counts)
  56. def calculate_meeting_counts(meetings):
  57. now = datetime.datetime.utcnow().replace(tzinfo=pytz.utc)
  58. meeting_counts = {}
  59. for hour in range(24):
  60. meeting_counts[hour] = {k: [] for k in WEEKDAYS}
  61. for meeting in meetings:
  62. if 'meeting_id' in meeting:
  63. meeting_id = ('http://{}/meetings/{}/{:4d}/?C=N;O=D'.format(
  64. EAVESDROP, meeting['meeting_id'].replace('-', '_'), now.year))
  65. else:
  66. meeting_id = meeting['filefrom']
  67. for schedule in meeting['schedule']:
  68. try:
  69. day = schedule['day']
  70. time = schedule['time']
  71. frequency = schedule['frequency']
  72. week_count = WEEK_COUNTS[frequency]
  73. irc = schedule['irc']
  74. except KeyError:
  75. print("KeyError in here somewhere!")
  76. print("meeting = {}".format(meeting))
  77. print("schedule = {}".format(schedule))
  78. print("\n")
  79. continue
  80. hour = int(time[:-2])
  81. mins = int(time[-2:])
  82. duration = int(schedule.get('duration', 60))
  83. if duration > 60:
  84. print("Meeting longer than 60 minutes. We don't understand "
  85. "that yet")
  86. print("meeting = {}".format(meeting))
  87. print("schedule = {}".format(schedule))
  88. print("\n")
  89. if irc not in CHANNELS:
  90. # Handy for debugging
  91. # print("{}: {}".format(meeting['filefrom'], schedule))
  92. continue
  93. meeting_info = (
  94. "{:<13} - {}/{} - {:<21} - {}".format(
  95. frequency, time, duration, irc, meeting_id))
  96. # This is a little hacky way to handle alternating meetings. The
  97. # "counts" gathered are per fortnight so a weekly meeting takes up
  98. # 2 slots, and an alternating (biweekly-*) only one. This means
  99. # that a "full slot" will be one that has 8 (2 * number of meeting
  100. # channels) scheduled meetings
  101. for i in range(week_count):
  102. meeting_counts[hour][day].append(meeting_info)
  103. # Check for and record meetings that cross multiple hours (This
  104. # assumes that we don't have any meetings that are longer than
  105. # 60mins)
  106. if (mins + duration) > 60:
  107. meeting_counts[(hour+1) % 24][day].append(meeting_info)
  108. return meeting_counts
  109. def write_csv_file(filename, meeting_counts):
  110. filename = os.path.abspath(os.path.expanduser(filename))
  111. with open(filename, 'w') as out_file:
  112. writer = csv.writer(out_file)
  113. writer.writerow(["Hour"] + WEEKDAYS)
  114. for hour in range(24):
  115. row = [hour] + [len(meeting_counts[hour][day]) for day in WEEKDAYS]
  116. writer.writerow(row)
  117. print("Created CSV file of meeting slot usage at: {}".format(filename))
  118. def read_meetings(meeting_directory):
  119. meetings = []
  120. if not os.path.isdir(meeting_directory):
  121. sys.exit("Unable to find meeting directory: {}".format(
  122. meeting_directory))
  123. for dirpath, dirnames, filenames in os.walk(meeting_directory):
  124. for file_name in filenames:
  125. if not file_name.endswith('.yaml'):
  126. continue
  127. full_path = os.path.join(dirpath, file_name)
  128. with open(full_path, 'r') as f_obj:
  129. obj = yaml.safe_load(f_obj)
  130. obj['filefrom'] = full_path
  131. meetings.append(obj)
  132. return meetings
  133. def parse_args():
  134. parser = argparse.ArgumentParser(
  135. description='Check meeting count time usage')
  136. parser.add_argument(
  137. '--csv', metavar='FILE_NAME',
  138. help='If specified, write counts to the specified CSV file')
  139. parser.add_argument(
  140. '--sensitivity', type=int, default=1,
  141. help='Sensitivity of reporting. '
  142. 'Defaults to 1, which means report if no weekly slot is '
  143. 'available at the time slots considered.')
  144. args = parser.parse_args()
  145. return args
  146. if '__main__' == __name__:
  147. sys.exit(main())