commit 95b564a6e320157789c7dfc51429eeea4fba863e Author: sqozz Date: Fri May 11 02:15:08 2018 +0200 Add initial PoC diff --git a/schlafana.py b/schlafana.py new file mode 100644 index 0000000..739753f --- /dev/null +++ b/schlafana.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python +import pdb +import json +import logging +from pprint import pprint +from websocket import create_connection +from prometheus_client import Counter, Summary, start_http_server +from prometheus_client.core import CounterMetricFamily, REGISTRY +import time + +MESSAGES = { "GameInfo" : 0x00, + "WorldUpdate" : 0x01, + "BotSpawn" : 0x20, + "BotKill" : 0x21, + "BotMove" : 0x22, + "BotStats" : 0x24 + } + + +class BotDeathCollector(object): + def collect(self): + c = CounterMetricFamily("deaths", "Deaths of each player", labels=["name", "bot_rev"]) + for user in game.users.users: + user = game.users.users.get(user) + c.add_metric([user.name, user.current_rev], user.deaths) + yield c + + +class BotKillCollector(object): + def collect(self): + c = CounterMetricFamily("kills", "Kills of each player", labels=["name", "bot_rev"]) + for user in game.users.users: + user = game.users.users.get(user) + c.add_metric([user.name, user.current_rev], user.kills) + yield c + + +class BotRevisionCountCollector(object): + def collect(self): + c = CounterMetricFamily("code_revs", "Number of code revisions", labels=["name"]) + for user in game.users.users: + user = game.users.users.get(user) + c.add_metric([user.name], len(user.revs)) + yield c + + +def main(): + global game + game = Game() + global logger + logger = logging.getLogger("schlafana") + logger.setLevel(logging.DEBUG) + + ch = logging.StreamHandler() + ch.setLevel(logging.DEBUG) + + formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") + ch.setFormatter(formatter) + + logger.addHandler(ch) + REGISTRY.register(BotDeathCollector()) + REGISTRY.register(BotKillCollector()) + REGISTRY.register(BotRevisionCountCollector()) + start_http_server(9000) + logger.info("Start prometheus scrape endpoint") + + while True: + try: + logger.warning("Trying to connect…") + ws = create_connection("wss://schlangen.bytewerk.org/websocket") + except Exception: + game.uptime.observe(0) + time.sleep(5) + try: + while True: + data = ws.recv() + game.uptime.observe(1) + game.parse_data(data) + except Exception: + time.sleep(0.01) + + +class Game(): + def __init__(self): + self.x = 0 + self.y = 0 + self.food_decay_per_frame = 0 + self.users = UserList() + self.uptime = Summary("api_up", "Status of the server API") + pass + + + def parse_data(self, data): + js = json.loads(data) + msg_type = js.get("t") + + typ = MESSAGES.get(msg_type, -1) + + if typ == MESSAGES["BotSpawn"]: + bot = js.get("bot") + self.users.update_by_db(db_id=bot.get("db_id"), data=bot) + user = self.users.get_user(bot.get("db_id")) + #print("{}({}): respawn".format(user.name, user.db_id)) + elif typ == MESSAGES["GameInfo"]: + pdb.set_trace() + game.x = 0 + game.y = 0 + elif typ == MESSAGES["BotKill"]: + try: + victim = self.users.get_user_by_rev(js.get("victim_id")) + killer = self.users.get_user_by_rev(js.get("killer_id")) + if js.get("killer_id") == js.get("victim_id"): + #print("☕ {} killed himself".format(victim.name)) + pass + else: + #print("☠ {} fragged {}".format(killer.name, victim.name)) + pass + killer.kills += 1 + victim.deaths += 1 + except AttributeError: + print("Error in kill: {}".format(js)) + pass + elif typ == MESSAGES["BotStats"]: + msg_data = js.get("data") + for rev in msg_data: + user = self.users.get_user_by_rev(rev) + print(user) + pdb.set_trace() + pass + elif typ == MESSAGES["WorldUpdate"]: + bots = js.get("bots") + for bot in bots: + bot = bots.get(bot) + self.users.update_by_db(bot.get("db_id"), bot) + + elif msg_type != "FoodSpawn" and msg_type != "WorldUpdate" and msg_type != "FoodConsume" and msg_type != "BotMoveHead" and msg_type != "Tick" and msg_type != "FoodDecay": + logging.fatal("Unhandled msg_type: {}".format(msg_type)) + + +class BotUser(): + def __init__(self, data): + self.iterations = [] + self.db_id = None + self.revs = {} + self.current_rev = None + self.__deaths = 0 + self.__kills = 0 + self.parse(data) + + @property + def kills(self): + return self.__kills + + @kills.setter + def kills(self, kills): + self.__kills = kills + self.revs.get(self.current_rev).kills += 1 + + @property + def deaths(self): + return self.__deaths + + @deaths.setter + def deaths(self, deaths): + self.__deaths = deaths + if self.__deaths % 2 == 0: + print("{} was already killed {} times".format(self.name, self.__deaths)) + self.revs.get(self.current_rev).deaths += 1 + + def parse(self, data): + self.db_id = data.get("db_id") + self.name = data.get("name") + self.dog_tag = data.get("dog_tag") + self.face = data.get("face") + self.revs.update({ data.get("id") : BotRevision(data) }) + self.current_rev = data.get("id") + + def update(self, data): + if self.current_rev in self.revs.keys(): + pass + else: + #print("new rev added") + pass + if self.current_rev != data.get("id"): + self.current_rev = data.get("id") + else: + print("{} new code-version".format(self.name)) + self.revs.update({ self.current_rev : BotRevision(data) }) + + +class BotRevision(): + def __init__(self, data): + self.id = data.get("id") + self.deaths = 0 + self.kills = 0 + self.natural_food_consumed = 0 + self.carrion_food_consumed = 0 + self.hunted_food_consumed = 0 + pass + + def parse(self, data): + pass + + +class UserList(): + def __init__(self): + self.users = {} + + def update_by_db(self, db_id, data): + if data.get("db_id") in self.users.keys(): + self.users.get(data.get("db_id")).update(data) + else: + self.users.update({db_id : BotUser(data)}) + #print("{}({}): create user".format(self.users.get(db_id).name, db_id)) + + def get_user(self, db_id): + return self.users.get(db_id) + + def get_user_by_rev(self, rev_id): + for user in self.users: + user = self.users.get(user) + for rev in user.revs: + rev = user.revs.get(rev) + if rev.id == rev_id: + return user + + +main()