metrics.pl 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. #!/usr/bin/perl -w
  2. # vnstat-metrics.cgi -- Prometheus compatible metrics endpoint output from vnStat data
  3. # copyright (c) 2022 Teemu Toivola <tst at iki dot fi>
  4. # released under the GNU General Public License
  5. use strict;
  6. use JSON::PP;
  7. use HTTP::Daemon;
  8. use HTTP::Status;
  9. # location of vnstat binary
  10. my $vnstat_cmd = '/usr/bin/vnstat';
  11. my @data_resolutions = ('fiveminute', 'hour', 'day', 'month', 'year');
  12. # Create an HTTP::Daemon instance listening on port 9955
  13. my $d = HTTP::Daemon->new(
  14. LocalAddr => $ENV{'VNSTAT_METRICS_HOST'} // 'localhost',
  15. LocalPort => 9955,
  16. ) || die "Cannot create HTTP::Daemon instance: $!";
  17. print "HTTP server listening on ", $d->url, "\n";
  18. # Enter an event loop to handle incoming requests
  19. while (my $c = $d->accept) {
  20. while (my $r = $c->get_request) {
  21. if ($r->method eq 'GET') {
  22. my $response_text = generate_response();
  23. $c->send_response(HTTP::Response->new(RC_OK, undef, undef, $response_text));
  24. } else {
  25. $c->send_error(RC_FORBIDDEN);
  26. }
  27. }
  28. $c->close;
  29. undef($c);
  30. }
  31. sub generate_response {
  32. my $response = "Content-Type: text/plain\n\n";
  33. my $json_data = `$vnstat_cmd --json s 1`;
  34. my $data = "";
  35. eval { $data = decode_json($json_data) };
  36. if ($@) {
  37. $response .= "# Error: Invalid command output: $json_data\n";
  38. return $response;
  39. }
  40. if (not defined $data->{'vnstatversion'}) {
  41. $response .= "# Error: Expected content from command output missing\n";
  42. return $response;
  43. }
  44. if (not defined $data->{'interfaces'}[0]) {
  45. $response .= "# Error: No interfaces found in command output\n";
  46. return $response;
  47. }
  48. if (not defined $data->{'interfaces'}[0]{'created'}{'timestamp'}) {
  49. $response .= "# Error: Incompatible vnStat version used\n";
  50. return $response;
  51. }
  52. $response .= "# vnStat version: ".$data->{'vnstatversion'}."\n";
  53. $response .= print_totals($data);
  54. my @data_resolutions = ('fiveminute', 'hour', 'day', 'month', 'year');
  55. foreach my $data_resolution ( @data_resolutions ) {
  56. $response .= print_data_resolution($data_resolution, $data);
  57. }
  58. return $response;
  59. }
  60. sub print_totals {
  61. my ($data) = @_;
  62. my $output = "";
  63. $output .= "\n# HELP vnstat_interface_total_received_bytes All time total received (rx) bytes\n";
  64. $output .= "# TYPE vnstat_interface_total_received_bytes counter\n";
  65. foreach my $interface ( @{ $data->{'interfaces'} } ) {
  66. my $interface_alias = get_interface_alias($interface);
  67. $output .= "vnstat_interface_total_received_bytes{interface=\"$interface->{'name'}\",alias=\"$interface_alias\"} $interface->{'traffic'}{'total'}{'rx'} $interface->{'updated'}{'timestamp'}000\n";
  68. }
  69. $output .= "\n# HELP vnstat_interface_total_transmitted_bytes All time total transmitted (tx) bytes\n";
  70. $output .= "# TYPE vnstat_interface_total_transmitted_bytes counter\n";
  71. foreach my $interface ( @{ $data->{'interfaces'} } ) {
  72. my $interface_alias = get_interface_alias($interface);
  73. $output .= "vnstat_interface_total_transmitted_bytes{interface=\"$interface->{'name'}\",alias=\"$interface_alias\"} $interface->{'traffic'}{'total'}{'tx'} $interface->{'updated'}{'timestamp'}000\n";
  74. }
  75. return $output;
  76. }
  77. sub print_data_resolution {
  78. my ($resolution, $data) = @_;
  79. my $output_count = 0;
  80. my $output = "";
  81. $output .= "\n# HELP vnstat_interface_".$resolution."_received_bytes Received (rx) bytes for current $resolution\n";
  82. $output .= "# TYPE vnstat_interface_".$resolution."_received_bytes gauge\n";
  83. $output_count = 0;
  84. foreach my $interface ( @{ $data->{'interfaces'} } ) {
  85. my $interface_alias = get_interface_alias($interface);
  86. if (defined $interface->{'traffic'}{$resolution}) {
  87. $output .= "vnstat_interface_".$resolution."_received_bytes{interface=\"$interface->{'name'}\",alias=\"$interface_alias\"} $interface->{'traffic'}{$resolution}[0]{'rx'} $interface->{'updated'}{'timestamp'}000\n";
  88. $output_count++;
  89. }
  90. }
  91. if ($output_count == 0) {
  92. $output .= "# no data\n";
  93. }
  94. $output .= "\n# HELP vnstat_interface_".$resolution."_transmitted_bytes Transmitted (tx) bytes for current $resolution\n";
  95. $output .= "# TYPE vnstat_interface_".$resolution."_transmitted_bytes gauge\n";
  96. $output_count = 0;
  97. foreach my $interface ( @{ $data->{'interfaces'} } ) {
  98. my $interface_alias = get_interface_alias($interface);
  99. if (defined $interface->{'traffic'}{$resolution}) {
  100. $output .= "vnstat_interface_".$resolution."_transmitted_bytes{interface=\"$interface->{'name'}\",alias=\"$interface_alias\"} $interface->{'traffic'}{$resolution}[0]{'tx'} $interface->{'updated'}{'timestamp'}000\n";
  101. $output_count++;
  102. }
  103. }
  104. if ($output_count == 0) {
  105. $output .= "# no data\n";
  106. }
  107. return $output;
  108. }
  109. ################
  110. sub get_interface_alias
  111. {
  112. my ($interface) = @_;
  113. my $interface_alias = $interface->{'alias'};
  114. if (length($interface_alias) == 0) {
  115. $interface_alias = $interface->{'name'};
  116. }
  117. return $interface_alias;
  118. }
  119. print "Content-Type: text/plain\n\n";
  120. my $json_data = `$vnstat_cmd --json s 1`;
  121. my $data = "";
  122. eval { $data = decode_json($json_data) };
  123. if ($@) {
  124. print "# Error: Invalid command output: $json_data\n";
  125. exit 1;
  126. }
  127. if (not defined $data->{'vnstatversion'}) {
  128. print "# Error: Expected content from command output missing\n";
  129. exit 1;
  130. }
  131. if (not defined $data->{'interfaces'}[0]) {
  132. print "# Error: No interfaces found in command output\n";
  133. exit 1;
  134. }
  135. if (not defined $data->{'interfaces'}[0]{'created'}{'timestamp'}) {
  136. print "# Error: Incompatible vnStat version used\n";
  137. exit 1;
  138. }
  139. print "# vnStat version: ".$data->{'vnstatversion'}."\n";
  140. print_totals($data);
  141. foreach my $data_resolution ( @data_resolutions ) {
  142. print_data_resolution($data_resolution, $data);
  143. }