예제 #1
0
class TransmissionClient(object):
    def __init__(self, client_config: Dict[str, Any]) -> None:
        self.name = client_config["name"]
        del client_config["name"]
        self.address = f"{client_config['host']}:{client_config['port']}"
        self.client = Transmission(**client_config)
        self.connected = False

    def __repr__(self) -> str:
        return f"Transmission({self.name} [{self.address}])"

    def connect_if_necessary(self) -> None:
        if not self.connected:
            log.info(
                f"Connecting to transmission daemon {self.name} at {self.address}"
            )
            version = self.client.call("session-get",
                                       fields=["version"]).get("version")
            log.debug(
                f"{self.name} connected. Transmission version: {version}")
            self.connected = True

    def _create_empty_tracker_point(self, time: str,
                                    tracker: str) -> Dict[str, Any]:
        return {
            "measurement": "trackers",
            "time": time,
            "tags": {
                "client_name": self.name,
                "tracker": tracker
            },
            "fields": {
                "downloaded": 0,
                "uploaded": 0,
                "download_speed": 0,
                "upload_speed": 0,
                "connected_peers": 0,
                "seeding": 0,
                "downloading": 0,
                "stopped": 0,
                "errored": 0,
            },
        }

    def _get_client_stats_point(self, time: str) -> Dict[str, Any]:
        session = self.client.call("session-get",
                                   fields=["download-dir", "version"])
        version = session.get("version")
        free_space = self.client.call(
            "free-space", path=session.get("download-dir")).get("size-bytes")
        stats = self.client.call("session-stats")
        return {
            "measurement": "stats",
            "time": time,
            "tags": {
                "client_name": self.name,
                "version": version
            },
            "fields": {
                "free_space": free_space,
                "downloaded":
                stats.get("cumulative-stats").get("downloadedBytes"),
                "uploaded": stats.get("cumulative-stats").get("uploadedBytes"),
                "torrents": stats.get("torrentCount"),
                "download_speed": stats.get("downloadSpeed"),
                "upload_speed": stats.get("uploadSpeed"),
                # These counts are iterated when going through torrents below
                "seeding": 0,
                "downloading": 0,
                "errored": 0,
                "stopped": 0,
                "connected_peers": 0,
            },
        }

    def _get_historical_tracker_stats(self) -> Dict[str, Any]:
        tracker_stat_data = influxdb.get_kvp(TRACKER_STAT_STORAGE_KEY)
        if not tracker_stat_data:
            tracker_stat_data = "{}"
        historical_tracker_stats = json.loads(tracker_stat_data)
        if self.name not in historical_tracker_stats:
            historical_tracker_stats[self.name] = {}
        return historical_tracker_stats

    def get_data_points(self,
                        time: Optional[str] = None) -> List[Dict[str, Any]]:
        # TODO: Break this function up so it's not an ugly monolith
        self.connect_if_necessary()
        if time is None:
            time = utils.now()
        stats_point = self._get_client_stats_point(time)
        torrents = self.client.call(
            "torrent-get",
            fields=[
                "addedDate",
                "downloadedEver",
                "error",
                "hashString",
                "name",
                "peersConnected",
                "percentDone",
                "rateDownload",
                "rateUpload",
                "status",
                "trackers",
                "uploadedEver",
            ],
        ).get("torrents")
        historical_tracker_stats = self._get_historical_tracker_stats()
        tracker_points = {}
        points = []
        for torrent in torrents:
            tracker = ""
            # Only keep track of first tracker in any given torrent to not complicate tags
            match = url_domain_regex.match(
                torrent.get("trackers")[0].get("announce"))
            if match:
                tracker = match.group(3)
            else:
                log.error(
                    f"Torrent {torrent.get('name')} could not parse tracker. Not recording this data point"
                )
                continue
            if tracker not in tracker_points:
                tracker_points[tracker] = self._create_empty_tracker_point(
                    time, tracker)
            # Append stats for these torrents to their relevant tracker/client points
            if tracker not in historical_tracker_stats[self.name]:
                historical_tracker_stats[self.name][tracker] = {}
            historical_tracker_stats[self.name][tracker][get_unique_torrent_id(
                torrent.get("hashString"), torrent.get("addedDate"))] = {
                    "downloaded": torrent.get("downloadedEver"),
                    "uploaded": torrent.get("uploadedEver"),
                }
            status = get_status(torrent.get("status"))
            if status == "downloading":
                stats_point["fields"]["downloading"] += 1
                tracker_points[tracker]["fields"]["downloading"] += 1
            elif status == "seeding":
                stats_point["fields"]["seeding"] += 1
                tracker_points[tracker]["fields"]["seeding"] += 1
            elif status == "stopped":
                stats_point["fields"]["stopped"] += 1
                tracker_points[tracker]["fields"]["stopped"] += 1
            if torrent.get("error") != 0:
                stats_point["fields"]["errored"] += 1
                tracker_points[tracker]["fields"]["errored"] += 1
            tracker_points[tracker]["fields"]["download_speed"] += torrent.get(
                "rateDownload")
            tracker_points[tracker]["fields"]["upload_speed"] += torrent.get(
                "rateUpload")
            tracker_points[tracker]["fields"][
                "connected_peers"] += torrent.get("peersConnected")
            stats_point["fields"]["connected_peers"] += torrent.get(
                "peersConnected")
            # Create point for this individual torrent
            points.append({
                "measurement": "torrents",
                "time": time,
                "tags": {
                    "client_name": self.name,
                    "infohash": torrent.get("hashString"),
                    "torrent_name": torrent.get("name"),
                    "tracker": tracker,
                    "error": str(torrent.get("error")),
                    "status": status,
                },
                "fields": {
                    "downloaded": torrent.get("downloadedEver"),
                    "uploaded": torrent.get("uploadedEver"),
                    "download_speed": torrent.get("rateDownload"),
                    "upload_speed": torrent.get("rateUpload"),
                    "connected_peers": torrent.get("peersConnected"),
                    "percent_done": float(torrent.get("percentDone")),
                },
            })
        influxdb.write_kvp(TRACKER_STAT_STORAGE_KEY,
                           json.dumps(historical_tracker_stats))
        for tracker, tracker_torrent_stats in historical_tracker_stats[
                self.name].items():
            for _, values in tracker_torrent_stats.items():
                if tracker not in tracker_points:
                    tracker_points[tracker] = self._create_empty_tracker_point(
                        time, tracker)
                tracker_points[tracker]["fields"]["downloaded"] += values[
                    "downloaded"]
                tracker_points[tracker]["fields"]["uploaded"] += values[
                    "uploaded"]
            points.append(tracker_points[tracker])
        points.append(stats_point)
        return points