| 43 | |
| 44 | |
| 45 | class InfluxDBWrapper: |
| 46 | client = None |
| 47 | |
| 48 | def __init__(self, name): # type: ignore[no-untyped-def] |
| 49 | self.name = name |
| 50 | self.records = [] |
| 51 | |
| 52 | @classmethod |
| 53 | @functools.cache |
| 54 | def get_client(cls) -> InfluxDBClient: |
| 55 | """A singleton InfluxDB client instance""" |
| 56 | retries = Retry(connect=3, read=3, redirect=3) |
| 57 | return InfluxDBClient( |
| 58 | url=settings.INFLUXDB_URL, |
| 59 | token=settings.INFLUXDB_TOKEN, |
| 60 | org=settings.INFLUXDB_ORG, |
| 61 | retries=retries, |
| 62 | timeout=30000, # Hard stop to prevent hanging requests |
| 63 | ) |
| 64 | |
| 65 | @classmethod |
| 66 | def get_downsampled_bucket(cls, size: DownsampleSize) -> str: |
| 67 | return f"{settings.INFLUXDB_BUCKET}_downsampled_{size}" |
| 68 | |
| 69 | @classmethod |
| 70 | def select_downsampled_bucket(cls, date_start: datetime) -> str: |
| 71 | if (timezone.now() - date_start).days > 10: |
| 72 | return cls.get_downsampled_bucket(DownsampleSize.ONE_HOUR) |
| 73 | return cls.get_downsampled_bucket(DownsampleSize.FIFTEEN_MINUTES) |
| 74 | |
| 75 | def add_data_point( |
| 76 | self, |
| 77 | field_name: str, |
| 78 | field_value: str | int | float, |
| 79 | tags: typing.Mapping[ |
| 80 | str, |
| 81 | str | int | float, |
| 82 | ] |
| 83 | | None = None, |
| 84 | ) -> None: |
| 85 | point = Point(self.name) |
| 86 | point.field(field_name, field_value) |
| 87 | |
| 88 | if tags is not None: |
| 89 | for tag_key, tag_value in tags.items(): |
| 90 | point = point.tag(tag_key, tag_value) |
| 91 | |
| 92 | self.records.append(point) |
| 93 | |
| 94 | def write(self) -> None: |
| 95 | """Persist collected data points to InfluxDB""" |
| 96 | try: |
| 97 | self.get_client().write_api(write_options=SYNCHRONOUS).write( |
| 98 | bucket=settings.INFLUXDB_BUCKET, |
| 99 | record=self.records, |
| 100 | ) |
| 101 | except (HTTPError, InfluxDBError) as e: |
| 102 | logger.warning( |
no outgoing calls
searching dependent graphs…