From 4a03e7838ec2ef8ad30eb42c81b1137c069f5634 Mon Sep 17 00:00:00 2001 From: Tony Murray Date: Fri, 1 Dec 2017 01:53:26 -0600 Subject: [PATCH] feature: Save application metrics to db for alerting (#7828) * feature: save application metrics to db for alerting However, alerting will not work because ResolveGlues() is broken. Can add workaround after state_translations alerting is merged Does not update all applications yet, not sure if that should be done here or in another PR. Introduces two handy functions dbDeleteOrphans() and array_by_column(). Will replace those in other locations after this is merged or separate them out if this is not merged. * remove accidental inclusions * Add db schema --- includes/common.php | 12 +++++ includes/dbFacile.php | 27 +++++++++++ includes/discovery/applications.inc.php | 3 ++ includes/polling/applications/nginx.inc.php | 2 +- includes/polling/functions.inc.php | 54 +++++++++++++++++++-- misc/db_schema.yaml | 8 +++ sql-schema/219.sql | 2 + 7 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 sql-schema/219.sql diff --git a/includes/common.php b/includes/common.php index ac3192c2de..2eb1ba2bbc 100644 --- a/includes/common.php +++ b/includes/common.php @@ -1866,3 +1866,15 @@ function check_file_permissions($file, $mask) return ($perms & $mask) === $mask; } + +/** + * Index an array by a column + * + * @param array $array + * @param string|int $column + * @return array + */ +function array_by_column($array, $column) +{ + return array_combine(array_column($array, $column), $array); +} diff --git a/includes/dbFacile.php b/includes/dbFacile.php index 887ce45bd3..850239b145 100644 --- a/includes/dbFacile.php +++ b/includes/dbFacile.php @@ -292,6 +292,33 @@ function dbDelete($table, $where = null, $parameters = array()) }//end dbDelete() +/** + * Delete orphaned entries from a table that no longer have a parent in parent_table + * + * @param string $parent_table + * @param string $child_table + * @param string $id_column + * @return bool|int + */ +function dbDeleteOrphans($parent_table, $child_table, $id_column) +{ + global $database_link; + $time_start = microtime(true); + + $sql = "DELETE C FROM `$child_table` C"; + $sql .= " LEFT JOIN `$parent_table` P USING (`$id_column`)"; + $sql .= " WHERE P.`$id_column` IS NULL"; + + $result = dbQuery($sql, array()); + + recordDbStatistic('delete', $time_start); + if ($result) { + return mysqli_affected_rows($database_link); + } else { + return false; + } +} + /* * Fetches all of the rows (associatively) from the last performed query. * Most other retrieval functions build off this diff --git a/includes/discovery/applications.inc.php b/includes/discovery/applications.inc.php index 0abed304d6..7dfaf73e03 100644 --- a/includes/discovery/applications.inc.php +++ b/includes/discovery/applications.inc.php @@ -105,6 +105,9 @@ if ($num > 0) { } } +// clean application_metrics +dbDeleteOrphans('applications', 'application_metrics', 'app_id'); + echo PHP_EOL; unset( diff --git a/includes/polling/applications/nginx.inc.php b/includes/polling/applications/nginx.inc.php index 776d9dc114..91c128d6fa 100644 --- a/includes/polling/applications/nginx.inc.php +++ b/includes/polling/applications/nginx.inc.php @@ -11,7 +11,6 @@ if (!empty($agent_data['app'][$name])) { $nginx = snmp_get($device, '.1.3.6.1.4.1.8072.1.3.2.3.1.2.5.110.103.105.110.120', '-Ovq'); } $nginx = trim($nginx, '"'); -update_application($app, $nginx); echo ' nginx'; @@ -36,6 +35,7 @@ $fields = array( $tags = compact('name', 'app_id', 'rrd_name', 'rrd_def'); data_update($device, 'app', $tags, $fields); +update_application($app, $nginx, '', $fields); // Unset the variables we set here unset($nginx, $active, $reading, $writing, $waiting, $req, $rrd_name, $rrd_def, $tags); diff --git a/includes/polling/functions.inc.php b/includes/polling/functions.inc.php index c995d10d7d..fbc17da3ca 100644 --- a/includes/polling/functions.inc.php +++ b/includes/polling/functions.inc.php @@ -574,9 +574,10 @@ function location_to_latlng($device) * * @param array $app app from the db, including app_id * @param string $response This should be the full output - * @param string $current This is the current value we store in rrd for graphing + * @param string $status This is the current value for alerting + * @param array $metrics an array of additional metrics to store in the database for alerting */ -function update_application($app, $response, $current = '') +function update_application($app, $response, $status = '', $metrics = array()) { if (!is_numeric($app['app_id'])) { d_echo('$app does not contain app_id, could not update'); @@ -585,7 +586,7 @@ function update_application($app, $response, $current = '') $data = array( 'app_state' => 'UNKNOWN', - 'app_status' => $current, + 'app_status' => $status, 'timestamp' => array('NOW()'), ); @@ -603,6 +604,53 @@ function update_application($app, $response, $current = '') $data['app_state_prev'] = $app['app_state']; } dbUpdate($data, 'applications', '`app_id` = ?', array($app['app_id'])); + + // update metrics + if (!empty($metrics)) { + $db_metrics = dbFetchRows('SELECT * FROM `application_metrics` WHERE app_id=?', array($app['app_id'])); + $db_metrics = array_by_column($db_metrics, 'metric'); + + echo ': '; + foreach ($metrics as $metric_name => $value) { + if (!isset($db_metrics[$metric_name])) { + // insert new metric + dbInsert( + array( + 'app_id' => $app['app_id'], + 'metric' => $metric_name, + 'value' => $value, + ), + 'application_metrics' + ); + echo '+'; + } elseif ($value != $db_metrics[$metric_name]['value']) { + dbUpdate( + array( + 'value' => $value, + 'value_prev' => $db_metrics[$metric_name]['value'], + ), + 'application_metrics', + 'app_id=? && metric=?', + array($app['app_id'], $metric_name) + ); + echo 'U'; + } else { + echo '.'; + } + + unset($db_metrics[$metric_name]); + } + + // remove no longer existing metrics (generally should not happen + foreach ($db_metrics as $db_metric) { + dbDelete( + 'application_metrics', + 'app_id=? && metric=?', + array($app['app_id'], $db_metric['metric']) + ); + echo '-'; + } + } } function convert_to_celsius($value) diff --git a/misc/db_schema.yaml b/misc/db_schema.yaml index 78b18e2e30..8bb45adde9 100644 --- a/misc/db_schema.yaml +++ b/misc/db_schema.yaml @@ -133,6 +133,14 @@ applications: Indexes: PRIMARY: { Name: PRIMARY, Columns: [app_id], Unique: true, Type: BTREE } unique_index: { Name: unique_index, Columns: [device_id, app_type], Unique: true, Type: BTREE } +application_metrics: + Columns: + - { Field: app_id, Type: int(11), 'Null': false, Extra: '' } + - { Field: metric, Type: varchar(18), 'Null': false, Extra: '' } + - { Field: value, Type: int(11), 'Null': true, Extra: '' } + - { Field: value_prev, Type: int(11), 'Null': true, Extra: '' } + Indexes: + application_metrics_app_id_metric_uindex: { Name: application_metrics_app_id_metric_uindex, Columns: [app_id, metric], Unique: true, Type: BTREE } authlog: Columns: - { Field: id, Type: int(11), 'Null': false, Extra: auto_increment } diff --git a/sql-schema/219.sql b/sql-schema/219.sql new file mode 100644 index 0000000000..41559b915e --- /dev/null +++ b/sql-schema/219.sql @@ -0,0 +1,2 @@ +CREATE TABLE application_metrics (app_id INT(11) NOT NULL, metric VARCHAR(18) NOT NULL, value INT(11), value_prev INT(11)); +CREATE UNIQUE INDEX application_metrics_app_id_metric_uindex ON application_metrics (app_id, metric);