metrics.pl 5.9 KB

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