ffpb_netstatus.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. # -*- coding: utf-8 -*-
  2. from __future__ import print_function
  3. import willie
  4. import json
  5. import shelve
  6. import time
  7. import urllib2
  8. from ffpb import pretty_date
  9. from batcave import BatcaveClient
  10. __batcave = None
  11. highscores = None
  12. def setup(bot):
  13. """Called by willie upon loading this plugin."""
  14. global __batcave, highscores
  15. __batcave = BatcaveClient(bot.config.ffpb.batcave_url)
  16. # load highscores from disk
  17. highscores = shelve.open('highscoredata', writeback=True)
  18. if not 'nodes' in highscores:
  19. highscores['nodes'] = 0
  20. highscores['nodes_ts'] = time.time()
  21. if not 'clients' in highscores:
  22. highscores['clients'] = 0
  23. highscores['clients_ts'] = time.time()
  24. def shutdown(bot):
  25. """Called by willie upon loading this plugin."""
  26. global highscores
  27. # store highscores
  28. if not highscores is None:
  29. highscores.sync()
  30. highscores.close()
  31. highscores = None
  32. @willie.module.interval(15)
  33. def ffpb_get_stats(bot):
  34. """Fetch current statistics, if the highscore changes signal this."""
  35. status = __batcave.get_status()
  36. if status is None:
  37. print('Failed to fetch BATCAVE status.')
  38. return
  39. bot.memory['ffpb_stats'] = status
  40. (nodes_active, clients_count) = \
  41. (status['nodes_active'], status['clients_unique'])
  42. nodes_active += status.get('gateways_active', 0)
  43. highscore_changed = False
  44. if nodes_active > highscores['nodes']:
  45. highscores['nodes'] = nodes_active
  46. highscores['nodes_ts'] = time.time()
  47. highscore_changed = True
  48. if clients_count > highscores['clients']:
  49. highscores['clients'] = clients_count
  50. highscores['clients_ts'] = time.time()
  51. highscore_changed = True
  52. if highscore_changed:
  53. print('HIGHSCORE changed: {0} nodes ({1}), {2} clients ({3})'.format(
  54. highscores['nodes'],
  55. highscores['nodes_ts'],
  56. highscores['clients'],
  57. highscores['clients_ts'],
  58. ))
  59. if not bot.config.ffpb.msg_target is None:
  60. action_msg = 'notiert sich den neuen Highscore: {0} Knoten ({1}), {2} Clients ({3})'
  61. action_target = bot.config.ffpb.msg_target
  62. if not bot.config.ffpb.msg_target_public is None:
  63. action_target = bot.config.ffpb.msg_target_public
  64. bot.msg(action_target, '\x01ACTION %s\x01' % action_msg.format(
  65. highscores['nodes'], pretty_date(int(highscores['nodes_ts'])),
  66. highscores['clients'], pretty_date(int(highscores['clients_ts'])),
  67. ))
  68. @willie.module.commands('status')
  69. def ffpb_status(bot, trigger):
  70. """State of the network: count of nodes + clients"""
  71. stats = bot.memory.get('ffpb_stats')
  72. if stats is None:
  73. bot.say('Uff, kein Plan wo der Zettel ist. Fragst du später nochmal?')
  74. return
  75. gwactive = stats.get('gateways_active', 0)
  76. bot.say('Es sind {0} Knoten (inkl. {1} Gateways) und ca. {2} Clients online.'.format(
  77. stats["nodes_active"] + gwactive,
  78. gwactive,
  79. stats["clients_unique"]))
  80. @willie.module.commands('raw-status')
  81. def ffpb_batcave_status(bot, trigger):
  82. """State as given by BATCAVE."""
  83. status = __batcave.get_status()
  84. bot.say('Status: ' + str(json.dumps(status))[1:-1])
  85. @willie.module.commands('highscore')
  86. def ffpb_highscore(bot, trigger):
  87. """Print current highscores (nodes + clients)."""
  88. bot.say('Highscore: {0} Knoten ({1}), {2} Clients ({3})'.format(
  89. highscores['nodes'], pretty_date(int(highscores['nodes_ts'])),
  90. highscores['clients'], pretty_date(int(highscores['clients_ts']))))
  91. MAX_ROLLOUTSTATUS_LIST = 42
  92. @willie.module.commands('rollout-status')
  93. def ffpb_rolloutstatus(bot, trigger):
  94. """Display statistic on how many nodes have installed which firmware."""
  95. # initialize results dictionary
  96. result = {}
  97. skipped = 0
  98. arg = trigger.group(3)
  99. # inform users about changed command parameters
  100. if arg is not None and arg not in ['all', 'list']:
  101. bot.reply('Hm? !rollout-status [all|list <version>]')
  102. return
  103. # get all nodes
  104. nodes = __batcave.get_nodes()
  105. if nodes is None:
  106. bot.reply('Hmpf, ich kriege gerade keine Infos. Das ist doch Mist so.')
  107. return
  108. offlinenodes = 0
  109. count_offline = (arg == "all")
  110. if arg == 'list':
  111. list_nodes = trigger.group(4)
  112. if list_nodes is None or len(list_nodes) == 0:
  113. bot.reply('!rollout-status list <version>')
  114. return
  115. list_inactive = 0
  116. result = {'stable': [], 'testing': []}
  117. for item in nodes:
  118. release = item.get('firmware')
  119. if release != list_nodes:
  120. continue
  121. if item.get('status') not in ['active', 'stale']:
  122. list_inactive += 1
  123. continue
  124. name = item.get('name', item.get('node_id'))
  125. branch = item.get('autoupdater')
  126. if branch in result:
  127. result[branch].append(name)
  128. else:
  129. result[branch] = [name]
  130. total = sum([len(result[x]) for x in result])
  131. if total == 0:
  132. bot.reply('Niemand benutzt derzeit die Version "{version}".'.format(version=list_nodes))
  133. if list_inactive > 0:
  134. bot.say('Aber es wurden {count} inaktive/offline Knoten mit Version {version} gezählt.'.format(
  135. count=list_inactive,
  136. version=list_nodes,
  137. ))
  138. return
  139. if total > MAX_ROLLOUTSTATUS_LIST:
  140. bot.reply('Das betrifft {total} Knoten, mehr als {max} werte ich als IRC-Spam.'.format(total=total, max=MAX_ROLLOUTSTATUS_LIST))
  141. else:
  142. for branch, nodes in result.items():
  143. bot.say('{count} Knoten auf {version} "{branch}": {nodes}'.format(
  144. branch=branch,
  145. version=list_nodes,
  146. count=len(nodes),
  147. nodes=','.join(nodes),
  148. ))
  149. if list_inactive > 0:
  150. bot.say('Zudem wurden {count} inaktive/offline Knoten mit Version {version} gezählt.'.format(
  151. count=list_inactive,
  152. version=list_nodes,
  153. ))
  154. # respond to the user
  155. return
  156. # check each node in ALFRED data
  157. for item in nodes:
  158. if (not count_offline) and (item.get('status') not in ['active', 'stale']):
  159. offlinenodes += 1
  160. continue
  161. release = item.get('firmware')
  162. branch = item.get('autoupdater')
  163. enabled = branch != 'off'
  164. if release is None or branch is None:
  165. skipped += 1
  166. continue
  167. if not release in result or result[release] is None:
  168. result[release] = {'stable': None, 'testing': None, }
  169. if not branch in result[release] or result[release][branch] is None:
  170. result[release][branch] = {'auto': 0, 'manual': 0, 'total': 0, }
  171. result[release][branch]['total'] += 1
  172. mode = 'auto' if enabled else 'manual'
  173. result[release][branch][mode] += 1
  174. # respond to user
  175. releases = sorted([x for x in result])
  176. for release in releases:
  177. output = 'Rollout von \'{0}\':'.format(release)
  178. branches = sorted([x for x in result[release]])
  179. first = True
  180. for branch in branches:
  181. item = result[release][branch]
  182. if item is None:
  183. continue
  184. if not first:
  185. output += ','
  186. first = False
  187. auto_count = item['auto']
  188. manual_count = item['manual']
  189. output += ' {1} {0}'.format(branch, auto_count)
  190. if manual_count > 0:
  191. output += ' (+{0} manuell)'.format(manual_count)
  192. bot.say(output)
  193. # output count of nodes for which the autoupdater's branch and/or
  194. # firmware version could not be retrieved
  195. if skipped > 0:
  196. bot.say('plus {0} Knoten mit unklarem Status'.format(skipped))
  197. if not count_offline and offlinenodes > 0:
  198. bot.say('zudem wurden {0} Knoten ignoriert da sie nicht online sind'.format(offlinenodes))
  199. @willie.module.commands('providers')
  200. def ffpb_providers(bot, trigger):
  201. """Fetch the top 5 providers from BATCAVE."""
  202. providers = __batcave.get_providers()
  203. providers.sort(key=lambda x: x['count'], reverse=True)
  204. top5 = providers[:5]
  205. top5 = ['{0} ({1:.0f}%)'.format(x['name'], x['percentage']) for x in top5]
  206. bot.say('Unsere Top 5 Provider: ' + ', '.join(top5))