If you are using Pi-Hole you may find this script useful
Currently this is designed to run on the server that is running Pi-Hole but as I have more than one I want to monitor I will soon have an update for additional.
First make sure you update your /var/www/html/admin/scripts/pi-hole/php/data.php
Look at getSummaryData function and include Uptime function
<?php /* Pi-hole: A black hole for Internet advertisements * (c) 2017 Pi-hole, LLC (https://pi-hole.net) * Network-wide ad blocking via your own hardware. * * This file is copyright under the latest version of the EUPL. * Please see LICENSE file for your rights under this license. */ $log = array(); $setupVars = parse_ini_file("/etc/pihole/setupVars.conf"); $hosts = file_exists("/etc/hosts") ? file("/etc/hosts") : array(); // Check if pihole.log exists and is readable $logListName = checkfile("/var/log/pihole.log"); $log = new \SplFileObject($logListName); // Check if preEventHorizon exists and is readable $gravityListName = checkfile("/etc/pihole/list.preEventHorizon"); $gravity = new \SplFileObject($gravityListName); // whitelist.txt is optional and might not be there $whiteListFile = checkfile("/etc/pihole/whitelist.txt"); $whitelist = new \SplFileObject($whiteListFile); // blacklist.txt is optional and might not be there $blackListFile = checkfile("/etc/pihole/blacklist.txt"); $blacklist = new \SplFileObject($blackListFile); if(isset($setupVars["API_PRIVACY_MODE"])) { $privacyMode = $setupVars["API_PRIVACY_MODE"]; } else { $privacyMode = false; } // Check if time zone is set // https://github.com/pi-hole/AdminLTE/pull/394 if (!date_default_timezone_get("date.timezone")) { date_default_timezone_set("UTC"); } /******* Public Members ********/ function getSummaryData() { $server_up_time = Uptime(); $domains_being_blocked = gravityCount(); $dns_queries_today = countDnsQueries(); $ads_blocked_today = countBlockedQueries(); $ads_percentage_today = $dns_queries_today > 0 ? ($ads_blocked_today / $dns_queries_today * 100) : 0; return array( 'domains_being_blocked' => $domains_being_blocked, 'dns_queries_today' => $dns_queries_today, 'ads_blocked_today' => $ads_blocked_today, 'ads_percentage_today' => $ads_percentage_today, ); } function Uptime() { $str = @file_get_contents('/proc/uptime'); $num = floatval($str); $secs = $num % 60; $num = (int)($num / 60); $mins = $num % 60; $num = (int)($num / 60); $hours = $num % 24; $num = (int)($num / 24); $days = $num; return $days . " days, " . $hours . " hours, " . $mins . " minutes, " . $secs . " seconds"; } function getOverTimeData() { global $log; // Get log lines $dns_queries = getDnsQueries($log); // Get list of ad domains $gravity_domains = getGravity(); // Bin log entries separated into Domains and Ads in 1 hour intervals list($domains_over_time, $ads_over_time) = overTime($dns_queries, $gravity_domains); // Align arrays alignTimeArrays($ads_over_time, $domains_over_time); // Provide a minimal valid array if there have are no blocked // queries at all. Otherwise the output of the API is inconsistent. if(count($ads_over_time) == 0) { $ads_over_time = [1 => 0]; } return Array( 'domains_over_time' => $domains_over_time, 'ads_over_time' => $ads_over_time, ); } function getOverTimeData10mins() { global $log; // Get log lines $dns_queries = getDnsQueries($log); // Get list of ad domains $gravity_domains = getGravity(); // Bin log entries separated into Domains and Ads in 10 minute intervals list($domains_over_time, $ads_over_time) = overTime10mins($dns_queries, $gravity_domains); // Align arrays (in case there have been hours without ad queries) alignTimeArrays($ads_over_time, $domains_over_time); // Provide a minimal valid array if there have are no blocked // queries at all. Otherwise the output of the API is inconsistent. if(count($ads_over_time) == 0) { $ads_over_time = [1 => 0]; } return Array( 'domains_over_time' => $domains_over_time, 'ads_over_time' => $ads_over_time, ); } // Test if variable exists and is positive function ispositive(&$arg) { if(isset($arg)) { if($arg > 0) { return true; } return false; } return false; } function getTopItems($argument) { global $log,$setupVars,$privacyMode; // Process log file $dns_domains = getDnsQueryDomains($log); // Get list of ad domains $gravity_domains = getGravity(); // Exclude domains the user doesn't want to see if(isset($setupVars["API_EXCLUDE_DOMAINS"])) { excludeFromList($dns_domains, "API_EXCLUDE_DOMAINS"); } // Sort array in descending order arsort($dns_domains); // Prepare arrays and counters for Top Items $topDomains = []; $domaincounter = 0; $topAds = []; $adcounter = 0; // Default number of Top Items to show is 10 $qty = 10; // If argument is numeric, the user may want to // see a different number of entries if(is_numeric($argument)) { $qty = intval($argument); } // Process sorted domain names foreach ($dns_domains as $key => $value) { if(ispositive($gravity_domains[$key]) && $adcounter < $qty) { // New entry for Top Ads $topAds[$key] = $value; $adcounter++; } else if($domaincounter < $qty && !$privacyMode) { // New entry for Top Domains $topDomains[$key] = $value; $domaincounter++; } elseif($domaincounter >= $qty && $adcounter >= $qty) { // Already collected enough entries for both lists // Exit loop early break; } } return Array( 'top_queries' => $topDomains, 'top_ads' => $topAds, ); } function getRecentItems($qty) { global $log; $dns_queries = getDnsQueries($log); return Array( 'recent_queries' => getRecent($dns_queries, $qty) ); } function getIpvType() { global $log; $dns_queries = getDnsQueries($log); $queryTypes = array(); foreach($dns_queries as $query) { $info = trim(explode(": ", $query)[1]); $queryType = explode(" ", $info)[0]; if (isset($queryTypes[$queryType])) { $queryTypes[$queryType]++; } else { $queryTypes[$queryType] = 1; } } return $queryTypes; } function resolveIPs(&$array) { $hostarray = []; foreach ($array as $key => $value) { $hostname = gethostbyaddr($key); // If we found a hostname for the IP, replace it if($hostname) { // Generate HOST entry $hostarray["$hostname|$key"] = $value; } else { // Generate IP entry $hostarray[$key] = $value; } } $array = $hostarray; // Sort new array arsort($array); } function getForwardDestinations() { global $log, $setupVars; $forwards = getForwards($log); $destinations = array(); foreach ($forwards as $forward) { $exploded = explode(" ", trim($forward)); $dest = $exploded[count($exploded) - 1]; if (isset($destinations[$dest])) { $destinations[$dest]++; } else { $destinations[$dest] = 1; } } if(istrue($setupVars["API_GET_UPSTREAM_DNS_HOSTNAME"])) { resolveIPs($destinations); } return $destinations; } // Check for existance of variable // and test it only if it exists function istrue(&$argument) { $ret = false; if(isset($argument)) { if($argument) { $ret = true; } } return $ret; } function getQuerySources() { global $log, $setupVars; $dns_queries = getDnsQueries($log); $sources = array(); foreach($dns_queries as $query) { $exploded = explode(" ", $query); $ip = trim($exploded[count($exploded)-1]); if (isset($sources[$ip])) { $sources[$ip]++; } else { $sources[$ip] = 1; } } global $setupVars; if(isset($setupVars["API_EXCLUDE_CLIENTS"])) { excludeFromList($sources, "API_EXCLUDE_CLIENTS"); } arsort($sources); $sources = array_slice($sources, 0, 10); if(istrue($setupVars["API_GET_CLIENT_HOSTNAME"])) { resolveIPs($sources); } return Array( 'top_sources' => $sources ); } $showBlocked = false; $showPermitted = false; function setShowBlockedPermitted() { global $showBlocked, $showPermitted, $setupVars; if(isset($setupVars["API_QUERY_LOG_SHOW"])) { if($setupVars["API_QUERY_LOG_SHOW"] === "all") { $showBlocked = true; $showPermitted = true; } elseif($setupVars["API_QUERY_LOG_SHOW"] === "permittedonly") { $showBlocked = false; $showPermitted = true; } elseif($setupVars["API_QUERY_LOG_SHOW"] === "blockedonly") { $showBlocked = true; $showPermitted = false; } elseif($setupVars["API_QUERY_LOG_SHOW"] === "nothing") { $showBlocked = false; $showPermitted = false; } else { // Invalid settings, show everything $showBlocked = true; $showPermitted = true; } } else { $showBlocked = true; $showPermitted = true; } } function getAllQueries($orderBy) { global $log,$showBlocked,$showPermitted,$privacyMode,$setupVars; $allQueries = array("data" => array()); $dns_queries = getDnsQueries($log); $hostnames=array(); // Create empty array for gravity $gravity_domains = getGravity(); $wildcard_domains = getWildcardListContent(); if(isset($_GET["from"])) { $from = new DateTime($_GET["from"]); } if(isset($_GET["until"])) { $until = new DateTime($_GET["until"]); } setShowBlockedPermitted(); // Privacy mode? if($privacyMode) { $showPermitted = false; } if(!$showBlocked && !$showPermitted) { // Nothing to do for us here return []; } foreach ($dns_queries as $query) { $time = new DateTime(substr($query, 0, 16)); // Check if we want to restrict the time where we want to show queries if(isset($from)) { if($time <= $from) { continue; } } if(isset($until)) { if($time >= $until) { continue; } } // print_r([$time->getTimestamp(),$_GET["from"],$_GET["until"]]); $exploded = explode(" ", trim($query)); $domain = $exploded[count($exploded)-3]; $status = ""; if(isset($gravity_domains[$domain])) { if($gravity_domains[$domain] > 0) { // Exact matching gravity domain $status = "Pi-holed (exact)"; } else { // Explicitly whitelisted $status = "OK (whitelisted)"; } } else { // Test for wildcard blocking foreach ($wildcard_domains as $entry) { if(strpos($domain, $entry) !== false) { $status = "Pi-holed (wildcard)"; } } if(!strlen($status)) { $status = "OK"; } } if((substr($status,0,2) === "Pi" && $showBlocked) || (substr($status,0,2) === "OK" && $showPermitted)) { $type = substr($exploded[count($exploded)-4], 6, -1); if(istrue($setupVars["API_GET_CLIENT_HOSTNAME"])) { $ip = $exploded[count($exploded) - 1]; if (isset($hostnames[$ip])) { $client = $hostnames[$ip]; } else { $hostnames[$ip] = gethostbyaddr($ip); $client = $hostnames[$ip]; } } else { $client = $exploded[count($exploded)-1]; } if($orderBy == "orderByClientDomainTime"){ $allQueries['data'][hasHostName($client)][$domain][$time->format('Y-m-d T H:i:s')] = $status; }elseif ($orderBy == "orderByClientTimeDomain"){ $allQueries['data'][hasHostName($client)][$time->format('Y-m-d T H:i:s')][$domain] = $status; }elseif ($orderBy == "orderByTimeClientDomain"){ $allQueries['data'][$time->format('Y-m-d T H:i:s')][hasHostName($client)][$domain] = $status; }elseif ($orderBy == "orderByTimeDomainClient"){ $allQueries['data'][$time->format('Y-m-d T H:i:s')][$domain][hasHostName($client)] = $status; }elseif ($orderBy == "orderByDomainClientTime"){ $allQueries['data'][$domain][hasHostName($client)][$time->format('Y-m-d T H:i:s')] = $status; }elseif ($orderBy == "orderByDomainTimeClient"){ $allQueries['data'][$domain][$time->format('Y-m-d T H:i:s')][hasHostName($client)] = $status; }else{ array_push($allQueries['data'], array( $time->format('Y-m-d T H:i:s'), $type, $domain, hasHostName($client), $status, "" )); } } } return $allQueries; } function tailPiholeLog($param) { // Not using SplFileObject here, since direct // usage of f-streams will be much faster for // files as large as the pihole.log global $logListName; $file = fopen($logListName,"r"); $offset = intval($param); if($offset > 0) { // Seeks on the file pointer where we want to continue reading is known fseek($file, $offset); $lines = []; while (!feof($file)) { array_push($lines,fgets($file)); } return ["offset" => ftell($file), "lines" => $lines]; } else { // Locate the current position of the file read/write pointer fseek($file, -1, SEEK_END); // Add one to skip the very last "\n" in the log file return ["offset" => ftell($file)+1]; } fclose($file); } /******** Private Members ********/ function gravityCount() { global $gravityListName,$blackListFile; $preEventHorizon = exec("grep -c ^ $gravityListName"); $blacklist = exec("grep -c ^ $blackListFile"); return ($preEventHorizon + $blacklist); } function getDnsQueries(\SplFileObject $log) { $log->rewind(); $lines = []; foreach ($log as $line) { if(strpos($line, ": query[A") !== false) { $lines[] = $line; } } return $lines; } function getDnsQueryDomains(\SplFileObject $log) { $log->rewind(); $domains = []; foreach ($log as $line) { if(strpos($line, ": query[A") !== false) { $exploded = explode(" ", $line); $domain = trim($exploded[count($exploded) - 3]); if (isset($domains[$domain])) { $domains[$domain]++; } else { $domains[$domain] = 1; } } } return $domains; } function countDnsQueries() { global $logListName; return intval(exec("grep -c \": query\\[A\" $logListName")); } function getDnsQueriesAll(\SplFileObject $log) { $log->rewind(); $lines = []; foreach ($log as $line) { if(strpos($line, ": query[A") || strpos($line, "gravity.list") || strpos($line, ": forwarded") !== false) { $lines[] = $line; } } return $lines; } function getDomains($file, &$array, $action){ $file->rewind(); foreach ($file as $line) { // Strip newline (and possibly carriage return) from end of key $key = rtrim($line); // if $action = true -> we want that domain to be ADDED to the list // doesn't harm to do this if it has already been set before // (e.g. once in gravity list, once in blacklist) if($action && strlen($key) > 0) { // $action is true (we want to add) *and* key is not empty $array[$key] = 1; } elseif(!$action && isset($array[$key])) { // $action is false (we want to remove) *and* key is set $array[$key] = -1; } } } function getWildcardListContent() { $rawList = file_get_contents(checkfile("/etc/dnsmasq.d/03-pihole-wildcard.conf")); $wclist = explode("\n", $rawList); $list = []; foreach ($wclist as $entry) { $expl = explode("/", $entry); if(count($expl) == 3) { array_push($list,$expl[1]); } } return array_unique($list); } function getGravity() { global $gravity,$whitelist,$blacklist; $domains = []; // ADD (true) preEventHorizon domains getDomains($gravity, $domains, true); // ADD (true) blacklist domains getDomains($blacklist, $domains, true); // REMOVE (false) whitelist domains getDomains($whitelist, $domains, false); return $domains; } function getBlockedQueries(\SplFileObject $log) { $log->rewind(); $lines = []; foreach ($log as $line) { $exploded = explode(" ", str_replace(" "," ",$line)); if(count($exploded) == 8 || count($exploded) == 10) { // Structure of data is currently like: // Array // ( // [0] => Dec // [1] => 19 // [2] => 11:21:51 // [3] => dnsmasq[2584]: // [4] => /etc/pihole/gravity.list // [5] => doubleclick.com // [6] => is // [7] => ip.of.pi.hole // ) // with extra logging enabled // Array // ( // [0] => Dec // [1] => 19 // [2] => 11:21:51 // [3] => dnsmasq[2584]: // [4] => 1 (identifier) // [5] => 1.2.3.4/12345 // [6] => /etc/pihole/gravity.list // [7] => doubleclick.com // [8] => is // [9] => ip.of.pi.hole // ) $list = $exploded[count($exploded)-4]; $is = $exploded[count($exploded)-2]; // Consider only gravity.list as DNS source (not e.g. hostname.list) if(substr($list, strlen($list) - 12, 12) === "gravity.list" && $is === "is") { $lines[] = $line; }; } } return $lines; } function countBlockedQueries() { global $logListName; // Blocked due to gravity entries (ad lists + blacklist) $gravityblocked = intval(exec("grep -c -e \"gravity\.list.*is\" $logListName")); // Blocked due to wildcard entries $wildcard_domains = getWildcardListContent(); $wildcardblocked = 0; foreach ($wildcard_domains as $domain) { $wildcardblocked += intval(exec("grep -c -e \"config.*$domain is\" $logListName")); } return $gravityblocked +$wildcardblocked; } function getForwards(\SplFileObject $log) { $log->rewind(); $lines = []; foreach ($log as $line) { if(strpos($line, ": forwarded") !== false) { $lines[] = $line; } } return $lines; } function excludeFromList(&$array,$key) { global $setupVars; $domains = explode(",",$setupVars[$key]); foreach ($domains as $domain) { if(isset($array[$domain])) { unset($array[$domain]); } } return $array; } function overTime($entries, $gravity_domains) { $byTimeDomains = []; $byTimeAds = []; foreach ($entries as $entry) { $time = date_create(substr($entry, 0, 16)); $hour = $time->format('G'); $exploded = explode(" ", $entry); $domain = trim($exploded[count($exploded) - 3]); if(ispositive($gravity_domains[$domain])) { if (isset($byTimeAds[$time])) { $byTimeAds[$time]++; } else { $byTimeAds[$time] = 1; } } if (isset($byTimeDomains[$time])) { $byTimeDomains[$time]++; } else { $byTimeDomains[$time] = 1; } } return [$byTimeDomains,$byTimeAds]; } function overTime10mins($entries, $gravity_domains=[]) { $byTimeDomains = []; $byTimeAds = []; foreach ($entries as $entry) { $time = date_create(substr($entry, 0, 16)); $hour = $time->format('G'); $minute = $time->format('i'); // 00:00 - 00:09 -> 0 // 00:10 - 00:19 -> 1 // ... // 12:00 - 12:10 -> 72 // ... // 15:30 - 15:39 -> 93 // etc. $time = ($minute-$minute%10)/10 + 6*$hour; $exploded = explode(" ", $entry); $domain = trim($exploded[count($exploded) - 3]); if(ispositive($gravity_domains[$domain])) { if (isset($byTimeAds[$time])) { $byTimeAds[$time]++; } else { $byTimeAds[$time] = 1; } } if (isset($byTimeDomains[$time])) { $byTimeDomains[$time]++; } else { $byTimeDomains[$time] = 1; } } return [$byTimeDomains,$byTimeAds]; } function alignTimeArrays(&$times1, &$times2) { if(count($times1) == 0 || count($times2) < 2) { return; } $max = max(array_merge(array_keys($times1), array_keys($times2))); $min = min(array_merge(array_keys($times1), array_keys($times2))); for ($i = $min; $i <= $max; $i++) { if (!isset($times2[$i])) { $times2[$i] = 0; } if (!isset($times1[$i])) { $times1[$i] = 0; } } ksort($times1); ksort($times2); } function getRecent($queries, $qty){ $recent = array(); foreach (array_slice($queries, -$qty) as $query) { $queryArray = array(); $exploded = explode(" ", $query); $time = date_create(substr($query, 0, 16)); $queryArray['time'] = $time->format('h:i:s a'); $queryArray['domain'] = trim($exploded[count($exploded) - 3]); $queryArray['ip'] = trim($exploded[count($exploded)-1]); array_push($recent, $queryArray); } return array_reverse($recent); } function hasHostName($var){ global $hosts; foreach ($hosts as $host){ $x = preg_split('/\s+/', $host); if ( $var == $x[0] ){ $var = $x[1] . "($var)"; } } return $var; } ?>
Single Server Statistics
NOTE: Install the prerequisites (Commented within the script below)
NOTE: The script below may be more up to date at https://gist.github.com/dkittell/74dc56f832ea2b7da1c9cc9fcbb766ca
#!/bin/sh # Pi-Hole Statistics # # # Created by David Kittell on 4/21/17. # clear # Install Prerequisites - Start # Install JQ # sudo apt-get install jq # Install IPCalc # sudo apt-get install ipcalc # Install Network Manager (nmcli) # sudo apt-get install network-manager # Install Prerequisites - Stop # Variables - Start json=$(curl -s -X GET http://127.0.0.1/admin/api.php?summaryRaw) #echo ${json} uptime=$(echo ${json} | jq ".server_up_time" | sed 's/"//g') domains=$(echo ${json} | jq ".domains_being_blocked") queries=$(echo ${json} | jq ".dns_queries_today") blocked=$(echo ${json} | jq ".ads_blocked_today") percentage=$(printf "%0.2f\n" $(echo ${json} | jq ".ads_percentage_today")) # Variables - Stop #lsb_release -a OS=$(lsb_release -i | cut -d ":" -f2 | tr -d '[:space:]') OSCode=$(lsb_release -c | cut -d ":" -f2 | tr -d '[:space:]') OSVer=$(lsb_release -r | cut -d ":" -f2 | tr -d '[:space:]') #echo "$OS $OSCode $OSVer" # Network Variables - Start netAdapter=$(nmcli device status | grep en | cut -d " " -f1) if [ -z "$netAdapter" ]; then netAdapter=$(nmcli device status | grep eth | cut -d " " -f1) fi #declare netAdapter=$(ip -o link show | awk '{print $2,$9}' | grep UP | cut -d ":" -f1) #echo $netAdapter netIP=$(/sbin/ip -o -4 addr list $netAdapter | awk '{print $4}' | cut -d/ -f1) #echo $netIP #declare netMask=$(ipcalc -m $netIP | cut -d '=' -f2) netMask=$(ifconfig "$netAdapter" | sed -rn '2s/ .*:(.*)$/\1/p') netCIDR=$(ipcalc $netIP/$netMask | grep "Netmask:" | cut -d "=" -f2 | cut -d " " -f2 | tr -d '[:space:]') netWork=$(ipcalc $netIP/$netMask | grep "Network:" | cut -d "/" -f1 | cut -d " " -f4 | tr -d '[:space:]') echo "$OS $OSCode $OSVer" echo " Hostname: $(hostname)" echo " System Uptime: ${uptime}" echo "Network Information" echo " Adapter: $netAdapter" echo " IP: $netIP" echo " Netmask: $netMask" echo " CIDR: $netWork/$netCIDR" echo "-----------------------------------------------------------------------" echo "Ad Filter Stats" echo " Total Blocked Hosts: ${domains}" echo " Total Queries: ${queries}" echo " Total Blocked: ${blocked} (${percentage}%)"
Last Updated on June 10, 2019