1#!/usr/local/bin/perl
2#
3# 2007-06-26
4# Written by Ghost
5#
6# 2008-04-16
7# Update: Wildcard version
8#
9# 2008-11-12
10# Update: Perl RCON system
11#
12# Configuration variables
13#
14#   srcdspass     - RCON password
15#   srcdssamples  - Number of samples to take (optional, default: 5)
16#   srcdsfpsmax   - Maximum FPS on the game server (optional, default: 1000)
17#
18# Magic markers - optional - used by installation scripts and
19# munin-config:
20#
21#%# family=contrib
22#%# capabilities=autoconf
23
24use strict;
25
26# Set library path correctly
27use File::Basename;
28if (-l $0) {
29    push(@INC, dirname(readlink($0)));
30}
31push(@INC, dirname($0));
32
33# Load Rcon module or exit with failure message
34if (!eval "require Rcon") {
35    print "Failed to load Rcon module. ";
36    print "Make sure Rcon.pm is copied to Munin plugins directory.\n";
37    exit 1;
38}
39
40# Parse hostname and port from the plugin filename
41my ($HOST, $PORT) = $0 =~ m/.*_([^:]+)_(\d+)$/;
42if (!defined($HOST) || !defined($PORT)) {
43    print "Could not parse server address from filename.\n";
44    exit 1;
45}
46
47# Load config variables or use default values
48my $PASS    = $ENV{srcdspass}    || "";
49my $SAMPLES = $ENV{srcdssamples} || 5;
50my $MAX     = $ENV{srcdsfpsmax}  || 1000;
51
52# Print config or do plugin test if asked
53my $arg = shift();
54if ($arg eq 'config') {
55    print_config();
56} elsif ($arg eq 'autoconf') {
57    test_service();
58}
59
60
61#
62# Main program starts here
63#
64
65my $fps_avg = 0;
66my @fps_samples;
67
68my $sock = Rcon::sock_connect($HOST, $PORT);
69if (!$sock) {
70    print "Could not open socket to $HOST:$PORT.\n";
71    exit 1;
72}
73if (!Rcon::rcon_auth($sock, $PASS)) {
74    print "Could not authenticate.\n";
75    exit 1;
76}
77
78for (my $i = 0; $i < $SAMPLES; $i++) {
79    my $reply = Rcon::rcon_command($sock, "stats");
80    if (!defined($reply)) {
81	print "Did not receive reply from server (sample $i).\n";
82	next;
83    }
84    my @reply = split(/\n/, $reply);
85
86    foreach my $statline (@reply) {
87	next if ($statline !~ m/\s*[\w+\.]+\s+[\w+\.]+\s+[\w+\.]+\s+\d+\s+\d+\s+[\w+\.]+\s+\d+/);
88	my ($cpu, $in, $out, $uptime, $users, $fps, $players) = ($statline =~ m/^\s*([\w+\.]+)\s+([\w+\.]+)\s+([\w+\.]+)\s+(\d+)\s+(\d+)\s+([\w+\.]+)\s+(\d+)/);
89
90	if (defined($fps)) {
91	    push(@fps_samples, $fps);
92	}
93    }
94    select(undef, undef, undef, 0.2); # Wait moment before next sample
95}
96
97# MEAN
98if (@fps_samples) {
99    foreach (@fps_samples) {
100        $fps_avg += int($_);
101    }
102    $fps_avg /= ($#fps_samples+1);
103    $fps_avg = int($fps_avg);
104    print "fps.value $fps_avg\n";
105}
106
107# MEDIAN
108#if (@fps_samples) {
109#    @fps_samples = sort {$a <=> $b} @fps_samples;
110#    my $median = int($#fps_samples / 2 + 0.5);
111#    $fps_avg = $fps_samples[$median];
112#}
113
114
115sub print_config {
116    print("graph_title Server FPS at $HOST:$PORT\n",
117	  "graph_args --base 1000\n",
118	  "graph_vlabel FPS\n",
119	  "graph_category games\n",
120	  "graph_info The number of frames per second generated by Source game server, such as HL2, CS:S and DoD:S.\n");
121
122    print ("fps.label FPS\n",
123	   "fps.min 0\n",
124	   "fps.max $MAX\n",
125	   "fps.type GAUGE\n");
126
127    exit 0;
128}
129
130
131sub test_service {
132    my $sock = Rcon::sock_connect($HOST, $PORT);
133    if (!$sock) {
134	print "no (could not open socket to $HOST:$PORT)\n";
135	exit 0;
136    }
137    if (!Rcon::rcon_auth($sock, $PASS)) {
138	print "no (could not authenticate)\n";
139	exit 0;
140    }
141    if (!defined(Rcon::rcon_command($sock, "stats"))) {
142	print "no (did not receive reply from server)\n";
143	exit 0;
144    }
145
146    print "yes\n";
147    exit 0;
148}
149