= 1.3.3. define('MUNIN_API_DRAW_LINESTACK1', 'LINESTACK1'); define('MUNIN_API_DRAW_LINESTACK2', 'LINESTACK2'); define('MUNIN_API_DRAW_LINESTACK3', 'LINESTACK3'); define('MUNIN_API_DRAW_AREASTACK', 'AREASTACK'); // Features server for plugin stats. define('MUNIN_API_SERVER_HOST', 'http://features.osinet.eu/'); define('MUNIN_API_SERVER_ENDPOINT', 'xmlrpc.php'); /** * Menu access callback for Munin config fetches. * * TODO: define rules, then code. For now, protect at the web server level. */ function _munin_api_access_config($module) { return TRUE; } /** * Menu access callback for Munin data fetches. * * TODO: define rules, then code. For now, protect at the web server level. */ function _munin_api_access_fetch($module) { return TRUE; } /** * Build the site information to report on the plugin popularity page. * * The site key is a reasonably unique host identifier obtained without * exposing private info. This is the same method as in core update module. * * @see _update_process_fetch_task() * * @return array * The datestamp and version keys default to 0 if the information is not * available, as happens with Git-based deployments. */ function _munin_api_get_report_info() { $module_info = drupal_parse_info_file(drupal_get_path('module', Munin::MODULE) . '/' . Munin::MODULE . '.info'); $plugins = module_implements(Munin::HOOK_INFO); $hidden = array(Munin::MODULE); foreach ($plugins as $plugin_name) { $plugin_info = module_invoke($plugin_name, Munin::HOOK_INFO); if (!empty($plugin_info['#private'])) { $hidden[] = $plugin_name; } } $reported = array_diff($plugins, $hidden); $ret = array( 'module' => Munin::MODULE, 'datestamp' => check_plain($module_info['datestamp'] ?? 0), 'plugins' => $reported, 'site-key' => drupal_hmac_base64($GLOBALS['base_url'], drupal_get_private_key()), 'version' => check_plain($module_info['version'] ?? '0'), ); return $ret; } /** * Finalize result pages for Munin interactions. * * - text content, not HTML, * - non cacheable, even for anonymous users. */ function _munin_api_page_closure($ret) { drupal_add_http_header('Content-type', 'text/plain'); // Prevent drupal_page_set_cache() in drupal_page_footer(). $GLOBALS['conf']['cache'] = 0; print $ret; drupal_page_footer(); exit(); } /** * Display and log an incorrect hook implementation. * * @param string $hook * The hook in which the error was triggered. * @param string $module * The module implementing the hook in which the error was triggered. * * @return string * An error string suitable for use as a controller result. */ function _munin_report_hook_error($hook, $module) { $message = t('Incorrect implementation of hook_!hook() in module @module.'); $params = array( '!hook' => $hook, '@module' => $module, ); drupal_set_message(format_string($message, $params), 'error'); watchdog(Munin::MODULE, $message, $params, WATCHDOG_ERROR); return '

' . l(t('Back'), Munin::R_REPORTS . "/${$module}") . '

'; } /** * Munin API watchdog implementation. * * Increase severity if proves are too frequent, liable to cause extra load on * the site. */ function _munin_api_watchdog_munin() { $seconds = munin_api_munin_api_fetch(Munin::PROBE_MUNIN, FALSE); $seconds = reset($seconds); $info = munin_api_munin_api_info(); $info = $info[Munin::PROBE_MUNIN]['munin_seconds']; $warning = explode(':', $info['#warning']); $critical = explode(':', $info['#critical']); if (/* $seconds < $critical[0] || */ $seconds > $critical[1]) { $level = WATCHDOG_CRITICAL; } elseif (/* $seconds < $warning[0] || */ $seconds > $warning[1]) { $level = WATCHDOG_WARNING; } else { $level = WATCHDOG_DEBUG; } // Meaning MORE urgent than debug. if ($level < WATCHDOG_DEBUG) { watchdog(Munin::MODULE, 'Munin last probe came @last seconds ago.', [ '@last' => $seconds, ], $level); } } /** * Form build for admin/config/munin_api. * * @param array $form_state * The form state. * * @return array * A form array. * * @throws \Exception */ function munin_api_admin_settings(array $form_state) { $default_path = variable_get(Munin::V_INIT_PATH, Munin::D_INIT_PATH); $form = []; // Add the fade-in/out effects. drupal_add_js(drupal_get_path('module', Munin::MODULE) . '/munin_api.js'); drupal_add_js([ Munin::MODULE => [ 'path' => $default_path, ], ], [ 'type' => 'setting', 'scope' => JS_DEFAULT, ]); $form['watchdog'] = [ '#type' => 'fieldset', '#title' => t('Munin Watchdog'), '#description' => t('Drupal-level monitoring of Munin'), ]; $form['watchdog'][Munin::V_WATCHDOG] = [ '#description' => t('If enabled, the module will regularly make sure the time interval between Munin probes is within the expected range and raise watchdog alerts accordingly. "Cron" mode is recommended. "Page init" mode should only be used on pages with a steady occurrence rate below 5 minutes but not too high, and will not work properly for anonymous users if expiration of cached pages is set to a non-zero value. This mechanism is most useful when watchdog is implemented by syslog, not dblog.', [ '!performance' => url('admin/config/development/performance'), ]), '#type' => 'radios', '#options' => [ Munin::V_WATCHDOG_NONE => t('Disabled'), Munin::V_WATCHDOG_CRON => t('On cron runs'), Munin::V_WATCHDOG_INIT => t('On page init'), ], '#default_value' => variable_get(Munin::V_WATCHDOG, Munin::D_WATCHDOG), ]; $form['watchdog'][Munin::V_INIT_PATH] = [ '#title' => t('Init path'), '#description' => t('For page init watchdog mode, specify the regular expression for the paths that should trigger a watchdog check'), '#type' => 'textfield', '#default_value' => $default_path, ]; $form['reporting'] = [ '#type' => 'fieldset', '#title' => t('Module reporting'), '#description' => t('Information reported by this module to the OSInet Features Server', [ '!ofs' => 'http://features.osinet.eu/munin-api-drupal', ]), ]; $form['reporting'][Munin::V_NEXT_REPORT] = [ '#type' => 'checkbox', '#title' => t('Report anonymous usage statistics'), '#default_value' => variable_get(Munin::V_NEXT_REPORT, 0), '#return_value' => REQUEST_TIME, '#description' => t('Opt-in for reporting usage data about Munin plugins without breaking the anonymity of the site. See the reported information below.'), ]; $info = _munin_api_get_report_info(); $header = [ t('Property'), t('Value'), t('Explanation'), ]; $rows = [ [ 'module', $info['module'], t('The project name of this module'), ], [ 'version', $info['version'], t('The official version of this module'), ], [ 'datestamp', $info['datestamp'], t('Consistency check for the version'), ], [ 'site-key', $info['site-key'], t('Your site unique key. This is the same identifier you are sending to drupal.org via update.module: it provides a reasonably unique identifier to count sites without reporting any identifiable information.'), ], [ 'plugins', theme('item_list', ['items' => $info['plugins']]), t('This shows which are the most popular plugins. Plugins with the #private attribute are considered private and not reported here.'), ], ]; $form['reporting']['info'] = [ '#theme' => 'table', '#header' => $header, '#rows' => $rows, '#attributes' => ['class' => ['munin-api-report']], ]; $form = system_settings_form($form); return $form; } /** * Implements hook_cron(). */ function munin_api_cron() { // Is the Munin-node watchdog enabled in cron mode ? if (variable_get(Munin::V_WATCHDOG, Munin::D_WATCHDOG) == Munin::V_WATCHDOG_CRON) { _munin_api_watchdog_munin(); } // Has the admin opted in for the anonymous usage report ? if ($next = variable_get(Munin::V_NEXT_REPORT, 0)) { if (REQUEST_TIME > $next) { $link = l(t('Browse server'), MUNIN_API_SERVER_HOST, ['external' => TRUE]); $ret = xmlrpc(MUNIN_API_SERVER_HOST . MUNIN_API_SERVER_ENDPOINT, 'ofe.usage', _munin_api_get_report_info()); if ($ret === FALSE) { watchdog(Munin::MODULE, 'XML-RPC error @errno: @message', array( '@errno' => xmlrpc_errno(), '@message' => xmlrpc_error_msg(), ), WATCHDOG_NOTICE, $link); } else { watchdog(Munin::MODULE, 'Plugin usage statistics uploaded', NULL, WATCHDOG_INFO, $link); // Set next update from current value of next, not from current time, // to avoid slow shifts over time. 604800 = 7 days. variable_set(Munin::V_NEXT_REPORT, $next + 604800); } } } } /** * Implements hook_help(). */ function munin_api_help($path, $arg) { switch ($path) { case 'admin/help#munin_api': if (empty($arg[0])) { return '

' . t('Most of the help for the Munin API modules is in Advanced Help', [ '!link' => url('admin/help/ah/munin_api'), ]) . '

'; } break; } } /** * Implements hook_init(). * * Only trigger Munin API watchdog on selected pages, and won't work on cached * pages unless $conf['page_cache_invoke_hooks'] is TRUE. * * @see _drupal_bootstrap_page_cache() */ function munin_api_init() { if (variable_get(Munin::V_WATCHDOG, Munin::D_WATCHDOG) == Munin::V_WATCHDOG_INIT) { $path = $_GET['q']; $regex = variable_get(Munin::V_INIT_PATH, Munin::D_INIT_PATH); $sts = preg_match($regex, $path); if ($sts) { _munin_api_watchdog_munin(); } } } /** * Implements hook_menu(). */ function munin_api_menu() { $items = []; $items[Munin::R_CONFIG] = [ 'title' => 'Munin', 'description' => 'Munin API settings', 'page callback' => 'drupal_get_form', 'page arguments' => ['munin_api_admin_settings'], 'access arguments' => ['administer site configuration'], ]; $items[Munin::R_REPORTS] = [ 'title' => 'Munin', 'description' => 'Reports about Munin data collectors and their probes', 'page callback' => 'munin_api_page_report_global', 'access arguments' => ['access site reports'], ]; $items[Munin::R_REPORTS . '/list'] = [ 'type' => MENU_DEFAULT_LOCAL_TASK, 'title' => 'General', 'weight' => -1, ]; foreach (module_implements(Munin::HOOK_INFO) as $module_name) { $module = module_invoke($module_name, Munin::HOOK_INFO); $graphs = element_children($module); $items[Munin::R_REPORTS . "/${module_name}"] = [ 'type' => MENU_LOCAL_TASK, 'title' => $module['#title'], 'description' => $module['#description'], 'page callback' => 'munin_api_page_report_instance', 'page arguments' => [$module_name, $module], 'access arguments' => ['access site reports'], ]; foreach ($graphs as $graph_name) { $items[Munin::R_BASE . "/${graph_name}"] = [ 'type' => MENU_CALLBACK, 'page callback' => 'munin_api_page_fetch', 'page arguments' => [$module_name, $module, $graph_name], 'access callback' => '_munin_api_access_fetch', 'access arguments' => [$module_name, $graph_name], ]; $items[Munin::R_BASE . "/${graph_name}/config"] = [ 'type' => MENU_CALLBACK, 'page callback' => 'munin_api_page_config', 'page arguments' => [$module_name, $graph_name], 'access callback' => '_munin_api_access_config', 'access arguments' => [$module_name, $graph_name], ]; } } ksort($items); return $items; } /** * Implements hook_munin_api_fetch(). */ function munin_api_munin_api_fetch($graph_name, $log = TRUE) { switch ($graph_name) { case Munin::PROBE_MUNIN: $requestTime = REQUEST_TIME; $last = variable_get(Munin::V_LAST, 0); $ret['munin_seconds'] = $last ? $requestTime - $last : 0; if ($log) { variable_set(Munin::V_LAST, $requestTime); } break; } return $ret; } /** * Implements hook_munin_api_info(). * * Returns an array of Munin probes information, indexed by probe name. */ function munin_api_munin_api_info() { $int = [ '#graph_printf' => "'%d'", ]; $ret = [ '#title' => t('Munin'), '#description' => t('Munin monitoring'), Munin::PROBE_MUNIN => [ '#title' => t('Munin-node'), '#info' => t('Monitor the monitoring solution.'), '#graph_vlabel' => t('Seconds since last probe'), 'munin_seconds' => $int + [ '#label' => t('Seconds since last probe'), '#type' => MUNIN_API_GAUGE, '#info' => t('Seconds since last probe should hover around 300. 0 means no probe found.'), '#warning' => '290:310', '#critical' => '1:600', ], ], ]; return $ret; } /** * Page callback for munin config fetches. */ function munin_api_page_config($module_name, $graph_name) { $module_info = module_invoke($module_name, Munin::HOOK_INFO); if (!is_array($module_info)) { return _munin_report_hook_error(Munin::HOOK_INFO, $module_name); } $info = $module_info[$graph_name]; $config = array( 'graph_title' => $info['#title'], 'graph_info' => $info['#info'], 'graph_category' => 'Drupal', ); foreach (element_properties($info) as $property_name) { if (!in_array($property_name, array('#title', '#info'))) { $config[drupal_substr($property_name, 1)] = $info[$property_name]; } } foreach (element_children($info) as $field_name) { foreach (element_properties($info[$field_name]) as $property_name) { $config[$field_name . '.' . drupal_substr($property_name, 1)] = $info[$field_name][$property_name]; } } $ret = ''; foreach ($config as $k => $v) { $ret .= $k . ' ' . $v . PHP_EOL; } _munin_api_page_closure($ret); } /** * Page callback for Munin data fetches. */ function munin_api_page_fetch($module_name, $module, $graph_name) { $data = module_invoke($module_name, Munin::HOOK_FETCH, $graph_name); if (!is_array($data)) { return _munin_report_hook_error(Munin::HOOK_FETCH, $module_name); } $ret = ''; foreach ($data as $field => $value) { $ret .= $field . '.value ' . $value . PHP_EOL; } _munin_api_page_closure($ret); } /** * Page callback for Munin global report. * * @return array * A render array for the page contents. * * @throws \Exception */ function munin_api_page_report_global() { $header = [ t('Module'), t('Graph'), t('Description'), t('Fields'), ]; $rows = []; foreach (module_implements(Munin::HOOK_INFO) as $module_name) { $info = module_invoke($module_name, Munin::HOOK_INFO); $rows[] = [ [ 'data' => l($info['#title'] ?? $module_name, Munin::R_REPORTS . "/${module_name}"), 'colspan' => 2, ], [ 'colspan' => 2, 'data' => $info['#description'] ?? '', ], ]; foreach (element_children($info) as $name) { $title = $info[$name]['#title'] ?? $name; $rows[] = [ ' ', $title, isset($info[$name]['#info']) ?? t('<missing>'), count(element_children($info[$name])), ]; } } $ret = [ '#theme' => 'table', '#header' => $header, '#rows' => $rows, ]; return $ret; } /** * Page callback for Munin instance report. * * @param string $module_name * The name of the module providing Munin data. * @param array $module_info * The information about the module. * * @return array * A render array for the report. * * @throws \Exception */ function munin_api_page_report_instance($module_name, array $module_info) { $header = [ t('Name'), t('Title / Description'), t('Type'), t('Debug'), ]; $error = ['class' => 'error']; $rows = []; foreach (element_children($module_info) as $graph_name) { $data = module_invoke($module_name, Munin::HOOK_FETCH, $graph_name, FALSE); if (!is_array($data)) { return _munin_report_hook_error(Munin::HOOK_FETCH, $module_name); } $rows[] = [ [ 'data' => $module_info[$graph_name]['#title'] ?? $graph_name, 'colspan' => 3, ], l(t('config'), Munin::R_BASE . "/${graph_name}/config"), ]; foreach (element_children($module_info[$graph_name]) as $field_name) { $rows[] = [ ' ', $module_info[$graph_name][$field_name]['#label'] ?? t('<missing>'), $module_info[$graph_name][$field_name]['#type'], $data[$field_name], ]; unset($data[$field_name]); } foreach ($data as $field_name => $field_value) { $rows[] = [ ' ', $error + ['data' => $field_name], $error + ['data' => '<unconfigured>'], $error + ['data' => $field_value], ]; } } $ret = [ '#theme' => 'table', '#header' => $header, '#rows' => $rows, ]; return $ret; }