Skip to content

Commit

Permalink
Add palworld_player_save_count metric
Browse files Browse the repository at this point in the history
  • Loading branch information
bostrt committed Feb 3, 2024
1 parent 864a69c commit 0e92167
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 4 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
# Prometheus Exporter for Palworld Server

Developed by https://palworld.lol/


This project contains a [Prometheus Exporter](https://prometheus.io/docs/instrumenting/exporters/) for [Palworld](https://store.steampowered.com/app/1623730/Palworld/) servers to monitor the following metrics:

| name | description | labels | metric type |
|------|-------------|--------|-------------|
| `palworld_player_count` | The current number of players on given server | no extra labels | Gauge |
| `palworld_player` | A player currently logged into the server | Character name, Player UID, and Steam ID | Gauge |
| `palworld_up` | Indicator if last metric scrape was successful | no extra labels | Gauge |
| `palworld_player_save_count` | Number of player save files on disk. Only included if `--save-directory` specified. | no extra labels | Gauge |

*For more information of [Gauges see here](https://prometheus.io/docs/concepts/metric_types/#gauge).*

# Options

```
```shell
$ palworld_exporter --help
Usage: palworld_exporter [OPTIONS]

Expand All @@ -27,6 +31,8 @@ Options:
listen on [default: 0.0.0.0]
--listen-port INTEGER Port for exporter to listen on [default:
9877]
--save-directory DIRECTORY Path to directory contain all .sav files
(e.g. Pal/Saved/SaveGames/0/2FCD4.../)
--log-level [NOTSET|DEBUG|INFO|WARNING|ERROR|CRITICAL]
Set logging level [default: INFO]
--ignore-logging-in Ignore players actively logging in that
Expand All @@ -42,6 +48,7 @@ Environment Variables are also available for each option above:
- `POLL_INTERVAL`
- `LISTEN_ADDRESS`
- `LISTEN_PORT`
- `SAVE_DIRECTORY`
- `LOG_LEVEL`
- `IGNORE_LOGGING_IN`

Expand Down
16 changes: 16 additions & 0 deletions src/palworld_exporter/collectors/save_collector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from glob import glob
import os


class SaveCollector:
def __init__(self, save_directory: str):
self.save_directory = save_directory

def player_save_count(self) -> int:
players_save_path = os.path.join(self.save_directory, "Players")
# Ensure Players directory exists
if not os.path.exists(players_save_path):
raise FileNotFoundError(
f'Player saves directory does not exist in: {self.save_directory}')
player_saves = glob(os.path.join(players_save_path, '*.sav'))
return len(player_saves)
31 changes: 29 additions & 2 deletions src/palworld_exporter/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from prometheus_client import Gauge

from palworld_exporter.collectors.rcon_collector import RCONCollector
from palworld_exporter.collectors.save_collector import SaveCollector

info_re = re.compile(r"\[v(?P<version>.*?)\](?P<name>.*$)")

Expand All @@ -15,14 +16,21 @@ class PalworldMetrics:
application metrics into Prometheus metrics.
"""

def __init__(self, rcon_host: str, rcon_port: int, rcon_password: str, polling_interval_seconds: int, ignore_logging_in: bool):
def __init__(self,
rcon_host: str,
rcon_port: int,
rcon_password: str,
polling_interval_seconds: int,
save_directory: str,
ignore_logging_in: bool):
self.rcon_host = rcon_host
self.rcon_port = rcon_port
self.rcon_password = rcon_password
self.polling_interval_seconds = polling_interval_seconds
self.save_directory = save_directory
self.ignore_logging_in = ignore_logging_in

# Prometheus metrics to collect
# Palworld server runtime metrics collected by RCON
self.player_count = Gauge(
'palworld_player_count', 'Current player count')
self.player_info = Gauge('palworld_player', 'Palworld player information', labelnames=[
Expand All @@ -32,13 +40,21 @@ def __init__(self, rcon_host: str, rcon_port: int, rcon_password: str, polling_i
self.palworld_up = Gauge(
'palworld_up', 'Was last scrape of Palworld metrics successful')

# Palworld server save file metrics collected from disk
self.player_save_count = Gauge(
'palworld_player_save_count', 'Number of player save files')

def run_metrics_loop(self):
"""Metrics fetching loop"""
while True:
self.fetch()
time.sleep(self.polling_interval_seconds)

def fetch(self):
self._rcon_fetch()
self._save_info_fetch()

def _rcon_fetch(self):
"""
Get metrics from application and refresh Prometheus metrics with
new values.
Expand Down Expand Up @@ -81,3 +97,14 @@ def fetch(self):
logging.exception(e)
finally:
self.palworld_up.set(success)

def _save_info_fetch(self):
if not self.save_directory:
# No save directory specified. Skip.
return

try:
sc = SaveCollector(self.save_directory)
self.player_save_count.set(sc.player_save_count())
except FileNotFoundError as e:
logging.error(e)
6 changes: 5 additions & 1 deletion src/palworld_exporter/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
@click.option('--poll-interval', default=5, help='How often to poll Palworld Server (seconds)', show_default=True, envvar='POLL_INTERVAL', type=int)
@click.option('--listen-address', default='0.0.0.0', help='Hostname or IP Address for exporter to listen on', envvar='LISTEN_ADDRESS', show_default=True)
@click.option('--listen-port', default=9877, help='Port for exporter to listen on', show_default=True, envvar='LISTEN_PORT', type=int)
@click.option('--save-directory', default=None, envvar='SAVE_DIRECTORY', help='Path to directory contain all .sav files (e.g. Pal/Saved/SaveGames/0/2FCD4.../)', show_default='None', type=click.Path(exists=True, dir_okay=True, file_okay=False))
@click.option('--log-level', type=LogLevel(), default='INFO', help='Set logging level', envvar='LOG_LEVEL', show_default=True)
@click.option('--ignore-logging-in', is_flag=True, default=True, envvar='IGNORE_LOGGING_IN', help='Ignore players actively logging in that temporarily have no Player UID')
def main(rcon_host: str,
Expand All @@ -22,14 +23,17 @@ def main(rcon_host: str,
poll_interval: int,
listen_address: str,
listen_port: int,
save_directory: str,
log_level: int,
ignore_logging_in: bool):

app_metrics = PalworldMetrics(
rcon_host=rcon_host,
rcon_port=rcon_port,
rcon_password=rcon_password,
polling_interval_seconds=poll_interval,
ignore_logging_in=True
save_directory=save_directory,
ignore_logging_in=ignore_logging_in
)

logging.basicConfig(format='%(asctime)s.%(msecs)03d %(levelname)-8s [%(filename)s:%(lineno)d] %(message)s',
Expand Down

0 comments on commit 0e92167

Please sign in to comment.