#!/usr/bin/perl -w
###############################################################################
#
# Webmin Sysstats Module
# Copyright (C) 2003 by Eric Gerbier
# Bug reports to: gerbier@users.sourceforge.net
# $Id$
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
###############################################################################
use strict;
use warnings;
use English qw(-no_match_vars);
unset_warnings();
require RRDs;
set_warnings();
# from web-lib.pl
## no critic (ProhibitPackageVars)
use vars qw(%config %text %in $in $cb $tb %access );
## use critic
my $module_name = get_mod_name(__FILE__);
my $fscript = $module_name . '.pl';
my $pre = 'vol';
my $data_name = 'total';
my $fkeys = 'mysql_keys';
my $EMPTY = EMPTY();
###############################################################################
sub install_module($$) {
my ( $minfo, $defaults ) = ( $_[0], $_[1] );
debug_sub_begin();
my %my_config = init_module_config( $module_name, 1 );
$my_config{'pre'} = $pre;
$my_config{'sample_rate'} = 2;
$my_config{'keep'} = 1; # semi-auto
$my_config{'host'} = 'localhost';
$my_config{'user'} = 'root';
$my_config{'password'} = $EMPTY;
$my_config{'port'} = 3306;
$my_config{'graph_type'} = 0;
init_param( $minfo, \%my_config );
write_config( $minfo, \%my_config, 1 );
install_script( $minfo, $fscript );
debug_sub_end();
return 1;
}
###############################################################################
# initialize parameters
sub init_param($$) {
my ( $minfo, $my_config ) = ( $_[0], $_[1] );
debug_sub_begin();
my $db_uid = $my_config->{'db_uid'};
my $vol_uid = get_max_vol($my_config);
# default selection (the parameter list is too long)
my @vol_list_def = (
'Aborted_clients', 'Aborted_connects',
'Bytes_received', 'Bytes_sent',
'Connections', 'Uptime',
);
my %tab = tab_param_mysql( $minfo, $my_config, 0 ); # no warning
my @vol_list = filter_known( $my_config, @vol_list_def );
if (@vol_list) {
my @colors = generate_colors();
foreach my $vol (@vol_list) {
debug("(init_param) new vol $vol");
create_base_mysql( $minfo, $my_config, $db_uid, $tab{$vol} );
$vol_uid++;
$my_config->{ $pre . $vol_uid } =
pack_config( $db_uid, $vol,
$colors[ ( $db_uid - 1 ) % ( scalar @colors ) ],
0, 0 );
$db_uid++;
}
$my_config->{'db_uid'} = $db_uid;
}
else {
debug('(init_param) no new vol');
}
return;
}
###############################################################################
sub graph_data($$) {
my ( $minfo, $my_config ) = ( $_[0], $_[1] );
debug_sub_begin();
my $cf = $my_config->{'cf'};
# take care of cgi parameters
my ( $graph_type, $c_display ) = get_cgi_graph($my_config);
my @rrd_detals;
gprint_title( $module_name, 'Parameters', \@rrd_detals );
my $vol_num = 1;
my $first_graph = 1;
VOL: while ( defined $my_config->{ $pre . $vol_num } ) {
my $vol_nb = $pre . $vol_num;
my ( $vol_id, $vol_name, $vol_config, $runstop, $display ) =
unpack_config( $my_config->{$vol_nb} );
if ($runstop) {
$vol_num++;
next VOL;
}
if ( $display == $c_display ) {
add_def( \@rrd_detals, $vol_nb, $minfo->{'dbtables'} . $vol_id,
$data_name, $cf );
if ( $graph_type == 0 ) {
# histograms
my $type;
if ($first_graph) {
$first_graph = 0;
$type = 'AREA';
}
else {
$type = 'STACK';
}
gprint_param( \@rrd_detals, $vol_nb, $vol_name, $vol_config,
$type );
}
else {
# lines
gprint_param( \@rrd_detals, $vol_nb, $vol_name, $vol_config );
}
}
$vol_num++;
}
graph_common( $minfo, $my_config, @rrd_detals );
debug_sub_end();
return;
}
###############################################################################
sub mod_config($$$) {
my ( $minfo, $my_config, $defaults ) = ( $_[0], $_[1], $_[2] );
debug_sub_begin();
mod_config_opt( $minfo, $my_config, $pre );
begin_html_config(1);
# general
change_database( $minfo, $my_config );
change_local_params($my_config);
change_display($my_config);
# local
change_module_config($my_config);
change_scale( $minfo, $my_config );
change_default( $my_config->{'graph_type'} );
my @columns = ( 'module_word_vol', 'database_type', );
end_html_config(@columns);
my %tab = tab_param_mysql( $minfo, $my_config, 1 );
my $vol_num = 1;
while ( defined $my_config->{ $pre . $vol_num } ) {
my ( $vol_id, $vol_name, $vol_config, $runstop, $display ) =
unpack_config( $my_config->{ $pre . $vol_num } );
my ( $vol_colour, $vol_line ) = get_line_config($vol_config);
begin_row($cb); # row 1
# name
td(
"<strong><a href=\"edit_module.cgi?idx=$in{'idx'}&opt=edit&vol_num=$vol_num\">$vol_name</a></strong>"
);
# database type
td( $tab{$vol_name} );
# color
display_colour($vol_colour);
# controls
print '<td align=right>';
display_parameter_line( $vol_num, $runstop, $display, $my_config,
$pre );
print "</td>\n";
end_row(); # row 1
$vol_num++;
}
end_table();
if ( ( exists $in{'opt'} ) and ( $in{'opt'} eq 'edit' ) ) {
my ( $vol_id, $vol_name, $vol_config, $runstop, $display ) =
unpack_config( $my_config->{ $pre . $in{'vol_num'} } );
begin_table( 'edition', 'border' ); # table 1
begin_row($tb); # row 1
title_row( $text{'config_edit_parameter'} );
end_row(); # row 1
begin_row($cb); # row 2
print '<td>';
print '<form action="save_config.cgi">';
print '<table ' . w100() . ">\n"; # table 2
# name
begin_row(); # row 3
title_row( $text{'module_word_vol'} );
td($vol_name);
end_row(); # row 3
# database type
begin_row(); # row 3
title_row( $text{'database_type'} );
td( $tab{$vol_name} );
end_row(); # row 3
modify_colour( $vol_config, $display, $runstop, $my_config, $pre );
print_button( $pre, $in{'idx'}, 'update_vol', $in{'vol_num'},
$text{'word_update'} );
print "</table>\n"; # table 2
print "</form>\n";
print "</td>\n";
end_row(); # row 2
end_table(); # table 1
}
else {
# new ones
my @vol_list = filter_known( $my_config, sort keys %tab );
begin_table( 'new_config', 'border' ); # table 1
begin_row($tb); # row 1
title_row( $text{'config_new_parameter'} );
end_row(); # row 1
begin_row($cb); # row 2
print '<td>';
print '<form action="save_config.cgi">';
print '<table ' . w100() . ">\n"; # table 2
# name
begin_row(); # row 3
title_row( $text{'module_word_vol'}, w15() );
my $i = 0;
print '<td nowrap><strong>';
print "<select name=\"new_vol_def\">\n";
print '<option value="' . $i++ . "\">$text{'word_select'}\n";
foreach my $vlist (@vol_list) {
print '<option value="' . $i++ . "\">$vlist\n";
}
print '</select>';
print '</strong></td>';
end_row(); # row 3
modify_colour( '00ff00', 0, 0, $my_config, $pre );
print_button( $pre, $in{'idx'}, 'new_vol', $vol_num,
$text{'word_addnew'} );
print "</table>\n"; # table 2
print "</form>\n";
print "</td>\n";
end_row(); # row 2
end_table(); # table 1
}
end_table();
print "</table>\n";
debug_sub_end();
return;
}
###############################################################################
sub mod_config_save($$$) {
my ( $minfo, $my_config, $defaults ) = ( $_[0], $_[1], $_[2] );
debug_sub_begin();
#info("debug : opt = $in{'opt'}");
## no critic (ProhibitCascadingIfElse)
if ( $in{'opt'} eq 'new_vol' ) {
if ( is_invalid_entry( $in{'new_vol_def'} ) or is_invalid_colour() ) {
error( $text{'save_einvaled_selection'} );
}
my %tab = tab_param_mysql( $minfo, $my_config, 1 );
my @vol_list = filter_known( $my_config, sort keys %tab );
my $new_vol = @vol_list[ $in{'new_vol_def'} - 1 ];
create_base_mysql( $minfo, $my_config, $my_config->{'db_uid'},
$tab{$new_vol} );
$my_config->{ $pre . $in{$pre} } = pack_config(
$my_config->{'db_uid'}++,
$new_vol,
set_line_config(
get_colourselect( 'vol_colour', $EMPTY ),
get_lineselect('vol_line')
),
0,
$in{'display_def'}
);
write_config( $minfo, $my_config );
myredirect("edit_module.cgi?idx=$in{'idx'}");
}
elsif ( $in{'opt'} eq 'update_vol' ) {
if ( ( $in{'vol_colour1_def'} eq 'nocolour' )
and ( $in{'vol_colour2_def'} eq $EMPTY ) )
{
error( $text{'save_einvaled_selection'} );
}
my ( $vol_id, $vol_name, $vol_colour, $runstop, $display ) =
unpack_config( $my_config->{ $pre . $in{$pre} } );
$my_config->{ $pre . $in{$pre} } = pack_config(
$vol_id,
$vol_name,
set_line_config(
get_colourselect( 'vol_colour', $EMPTY ),
get_lineselect('vol_line')
),
$in{'runstop_def'},
$in{'display_def'}
);
# test for display : force to create an dummy label
test_for_new_display($my_config);
write_config( $minfo, $my_config );
myredirect("edit_module.cgi?idx=$in{'idx'}");
}
elsif ( exists $in{'init'} ) {
init_param( $minfo, $my_config );
write_config( $minfo, $my_config );
}
elsif ( $in{'opt'} eq 'general' ) {
test_database_change( $minfo, $my_config );
test_local_params($my_config);
test_display_change($my_config);
test_scale_change( $minfo, $my_config );
test_default_change($my_config);
# configuration
test_module_config($my_config);
write_config( $minfo, $my_config );
}
else {
error($in);
}
debug_sub_end();
return;
}
###############################################################################
sub mysqladmin_cmd($$) {
my $my_config = $_[0];
my $arg = $_[1];
my $cmd = $my_config->{'mysqladmin'};
if ($cmd) {
# add user and password if exists
if ( exists $my_config->{'user'} ) {
$cmd .= ' --user=' . $my_config->{'user'};
}
# sol 1
# password on command line : not secure
# anyone can see the password using the ps command
# if ( $my_config->{'password'} ) {
#
# # quotemeta is used to escape special char in password
# $cmd .= ' --password=' . quotemeta $my_config->{'password'};
# }
# sol 2
# password by environment variable
$ENV{'MYSQL_PWD'} = $my_config->{'password'};
if ( $my_config->{'port'} ) {
$cmd .= ' --port=' . $my_config->{'port'};
}
if ( $my_config->{'host'} ) {
$cmd .= ' --host=' . $my_config->{'host'};
}
$cmd .= " $arg";
}
return $cmd;
}
###############################################################################
sub read_dynamic_param($$) {
my $my_config = $_[0];
my $flag_warn = $_[1];
my $cmd = mysqladmin_cmd( $my_config, 'extended-status' );
my %params;
if ($cmd) {
my $pipe = read_pipe_stream($cmd);
my $nb;
while (<$pipe>) {
$nb = 0 if ( !$nb );
if (m/Variable_name/) {
# title
next;
}
elsif (m/\s(\S+)\s+/) {
my $name = $1;
$params{$name} = 1;
$nb++;
}
}
# test result
if ( !$nb ) {
# no data read : problem !
my $msg;
if ( $my_config->{'runstop'} ) {
# already stopped
$msg = "$module_name module : can not read extended-status";
}
else {
# was running : stop it
$my_config->{'runstop'} = 1; # stop
$msg =
"disable $module_name module : can not read extended-status";
}
if ($flag_warn) {
warning($msg);
}
else {
info($msg);
}
}
}
else {
debug('unconfigured mysqladmin command');
}
## use critic
return %params;
}
###############################################################################
sub read_static_param($) {
debug_sub_begin();
my $minfo = $_[0];
my $fic = $minfo->{'mod_dir'} . $fkeys;
my %tab = read_awk($fic);
debug_sub_end();
return %tab;
}
###############################################################################
# get the list of status parameters
# from mysqladmin query if available
# else from static file fkeys
# get database type from fkeys
# have a default database type (GAUGE) for unknown parameters
sub tab_param_mysql($$$) {
debug_sub_begin();
my $minfo = $_[0];
my $my_config = $_[1];
my $flag_warn = $_[2]; # set to 1 to warn on error
# read static file
my %static_tab = read_static_param($minfo);
my %params;
# test if mysqladmin exists
my $cmd = $my_config->{'mysqladmin'};
if ($cmd) {
# get list of params from "mysqladmin extended-status" command
%params = read_dynamic_param( $my_config, $flag_warn );
if (%params) {
foreach my $param ( keys %params ) {
if ( exists $static_tab{$param} ) {
# get type from static
$params{$param} = $static_tab{$param};
}
else {
# get default type
$params{$param} = 'GAUGE';
debug("$param not found in static list mysql_keys");
}
}
}
else {
%params = %static_tab;
}
}
else {
# return static file
%params = %static_tab;
}
debug_sub_end();
return %params;
}
###############################################################################
sub create_base_mysql($$$) {
my ( $minfo, $my_config, $db_uid, $type ) = ( $_[0], $_[1], $_[2], $_[3] );
debug_sub_begin();
my $cf = $my_config->{'cf'};
my $heartbeat = compute_heartbeat( $my_config->{'sample_rate'} );
my $base = $minfo->{'dbtables'} . $db_uid . '.rrd';
$type = $type || 'DERIVE';
create_rrd( $base, "DS:$data_name:$type:$heartbeat:0:U", $cf );
debug_sub_end();
return;
}
###############################################################################
sub write_config($$;$) {
my ( $minfo, $my_config, $flag_nowarn ) = ( $_[0], $_[1], $_[2] );
debug_sub_begin();
check_for_runstop( $my_config, $flag_nowarn );
common_write_config( $minfo, $my_config );
debug_sub_end();
return;
}
###############################################################################
sub preupgrade($$$) {
my ( $minfo, $my_config, $defaults ) = ( $_[0], $_[1], $_[2] );
debug_sub_begin();
debug_sub_end();
return;
}
###############################################################################
sub postupgrade($$$) {
my ( $minfo, $my_config, $defaults ) = ( $_[0], $_[1], $_[2] );
debug_sub_begin();
debug_sub_end();
return;
}
###############################################################################
# get/set_display/runstop must be defined to allow mod_config_opt to work
sub toggle_display($) {
my $line = shift @_;
my ( $vol_id, $vol_name, $vol_config, $runstop, $display ) =
unpack_config($line);
$display = toggle_param_display($display);
$line = pack_config( $vol_id, $vol_name, $vol_config, $runstop, $display );
return $line;
}
###############################################################################
sub toggle_runstop($) {
my $line = shift @_;
my ( $vol_id, $vol_name, $vol_config, $runstop, $display ) =
unpack_config($line);
$runstop = toggle_param_runstop($runstop);
$line = pack_config( $vol_id, $vol_name, $vol_config, $runstop, $display );
return $line;
}
###############################################################################
# local config
###############################################################################
sub change_module_config($) {
my $my_config = $_[0];
debug_sub_begin();
titre_table('configuration');
begin_table('configuration');
# host
begin_row();
print_input( 'host', 'host', $my_config->{'host'}, 0, 0, 0 );
# port
print_input( 'port', 'port', $my_config->{'port'}, 0, 0, 0 );
end_row();
# user
begin_row();
print_input( 'user', 'user', $my_config->{'user'}, 0, 0, 0 );
# password
print_input( 'password', 'password', $my_config->{'password'},
0, 0, 0, 'password' );
end_row();
end_table(); # general
separator();
debug_sub_end();
return;
}
###############################################################################
sub test_module_config($) {
my $my_config = $_[0];
debug_sub_begin();
$my_config->{'host'} = $in{'host'};
$my_config->{'user'} = $in{'user'};
$my_config->{'password'} = $in{'password'};
$my_config->{'port'} = $in{'port'};
debug_sub_end();
return;
}
###############################################################################
# test parameters and configure runstop
sub check_for_runstop($;$) {
my $my_config = $_[0];
my $flag_nowarn = $_[1]; # optionnal
my $msg = ($flag_nowarn) ? \&info : \&warning;
# the best method to access data is with mysqladmin
# it avoids the perl DBI dependency, awfull code, and bugs
if ( !test_command( $my_config->{'mysqladmin'} ) ) {
# test "old" DBI methode
eval { require DBD::mysql };
if ($EVAL_ERROR) {
$my_config->{'runstop'} = 1;
&{$msg}(
"disable $module_name : can not find perl mysqladmin and DBI module"
);
goto FIN;
}
}
if ( !$my_config->{'user'} ) {
$my_config->{'runstop'} = 1;
&{$msg}("disable $module_name : user not configured");
}
elsif ( not search_process('mysqld') ) {
$my_config->{'runstop'} = 1;
&{$msg}( "disable $module_name : mysqld " . get_error() );
}
else {
common_check_for_runstop($my_config);
}
FIN: return;
}
###############################################################################
sub list_params($) {
my $my_config = $_[0];
return list_used_param($my_config);
}
###############################################################################
1;