metrics.pl 5.8 KB

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