From 06c361c2b529b20dfa6750072a1c1ef7a0803d9b Mon Sep 17 00:00:00 2001 From: Tony Murray Date: Wed, 28 Sep 2022 23:23:32 -0500 Subject: [PATCH] Cache version data (#14404) * Cache version data Calling cli commands can be expensive, cache the results. * style fixes * Fix pre-laravel usage * fix lint --- LibreNMS/Util/Version.php | 226 ++++++++++++++--------- app/Http/Controllers/AboutController.php | 2 +- app/Providers/AppServiceProvider.php | 3 + includes/common.php | 12 +- 4 files changed, 147 insertions(+), 96 deletions(-) diff --git a/LibreNMS/Util/Version.php b/LibreNMS/Util/Version.php index 930d613501..e2c4e883d8 100644 --- a/LibreNMS/Util/Version.php +++ b/LibreNMS/Util/Version.php @@ -26,6 +26,7 @@ namespace LibreNMS\Util; use DB; +use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Http\Client\ConnectionException; use Illuminate\Support\Arr; use Illuminate\Support\Str; @@ -38,19 +39,16 @@ class Version // Update this on release public const VERSION = '22.9.0'; - /** - * @var bool - */ - protected $is_git_install = false; - - public function __construct() - { - $this->is_git_install = Git::repoPresent() && Git::binaryExists(); - } + /** @var array */ + protected $cache = []; public static function get(): Version { - return new static; + try { + return app()->make('version'); + } catch (BindingResolutionException $e) { + return new static; // no container, just return a fresh instance + } } public function release(): string @@ -60,11 +58,23 @@ class Version public function local(): string { - if ($this->is_git_install && $version = $this->fromGit()) { - return $version; - } + return $this->cacheGet('local_version', function () { + if ($this->isGitInstall()) { + $version = rtrim(shell_exec('git describe --tags 2>/dev/null')); + if ($version) { + return $version; + } + } - return self::VERSION; + return self::VERSION; + }); + } + + public function isGitInstall(): bool + { + return $this->cacheGet('install_type', function () { + return (Git::repoPresent() && Git::binaryExists()) ? 'git' : 'other'; + }) == 'git'; } /** @@ -74,31 +84,43 @@ class Version */ public function localCommit(): array { - if ($this->is_git_install) { - $install_dir = base_path(); - $version_process = new Process(['git', 'show', '--quiet', '--pretty=%H|%ct'], $install_dir); - $version_process->run(); + return [ + 'sha' => $this->localCommitSha(), + 'date' => $this->localDate(), + 'branch' => $this->localBranch(), + ]; + } - // failed due to permissions issue - if ($version_process->getExitCode() == 128 && Str::startsWith($version_process->getErrorOutput(), 'fatal: unsafe repository')) { - (new Process(['git', 'config', '--global', '--add', 'safe.directory', $install_dir]))->run(); - $version_process->run(); + public function localCommitSha(): string + { + return $this->cacheGet('local_commit_sha', function () { + if (! $this->isGitInstall()) { + return ''; } - [$local_sha, $local_date] = array_pad(explode('|', rtrim($version_process->getOutput())), 2, ''); + return $this->localCommitData()[0] ?? ''; + }); + } - $branch_process = new Process(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], $install_dir); + public function localDate(): string + { + return $this->cacheGet('local_commit_date', function () { + return $this->localCommitData()[1] ?? ''; + }); + } + + public function localBranch(): string + { + return $this->cacheGet('local_branch', function () { + if (! $this->isGitInstall()) { + return ''; + } + + $branch_process = new Process(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], Config::get('install_dir')); $branch_process->run(); - $branch = rtrim($branch_process->getOutput()); - return [ - 'sha' => $local_sha, - 'date' => $local_date, - 'branch' => $branch, - ]; - } - - return ['sha' => null, 'date' => null, 'branch' => null]; + return rtrim($branch_process->getOutput()); + }); } /** @@ -108,16 +130,16 @@ class Version */ public function remoteCommit(): array { - if ($this->is_git_install && Config::get('update_channel') == 'master') { - try { - $github = \Http::withOptions(['proxy' => Proxy::forGuzzle()])->get(Config::get('github_api') . 'commits/master'); - - return $github->json(); - } catch (ConnectionException $e) { + return json_decode($this->cacheGet('remote_commit', function () { + if ($this->isGitInstall()) { + try { + return \Http::withOptions(['proxy' => Proxy::forGuzzle()])->get(Config::get('github_api') . 'commits/master')->body(); + } catch (ConnectionException $e) { + } } - } - return []; + return '[]'; + }), true); } public function databaseServer(): string @@ -156,53 +178,50 @@ class Version return ['last' => 'Not Connected', 'total' => 0]; } - private function fromGit(): string - { - return rtrim(shell_exec('git describe --tags 2>/dev/null')); - } - public function gitChangelog(): string { - return $this->is_git_install - ? rtrim(shell_exec('git log -10')) - : ''; - } - - public function gitDate(): string - { - return $this->is_git_install - ? rtrim(shell_exec("git show --pretty='%ct' -s HEAD")) - : ''; + return $this->cacheGet('changelog', function () { + return $this->isGitInstall() + ? rtrim(shell_exec('git log -10')) + : ''; + }); } public function python(): string { - $proc = new Process(['python3', '--version']); - $proc->run(); + return $this->cacheGet('python', function () { + $proc = new Process(['python3', '--version']); + $proc->run(); - if ($proc->getExitCode() !== 0) { - return ''; - } + if ($proc->getExitCode() !== 0) { + return ''; + } - return explode(' ', rtrim($proc->getOutput()), 2)[1] ?? ''; + return explode(' ', rtrim($proc->getOutput()), 2)[1] ?? ''; + }); } public function rrdtool(): string { - $process = new Process([Config::get('rrdtool', 'rrdtool'), '--version']); - $process->run(); - preg_match('/^RRDtool ([\w.]+) /', $process->getOutput(), $matches); + return $this->cacheGet('rrdtool', function () { + $process = new Process([Config::get('rrdtool', 'rrdtool'), '--version']); + $process->run(); + preg_match('/^RRDtool ([\w.]+) /', $process->getOutput(), $matches); - return str_replace('1.7.01.7.0', '1.7.0', $matches[1] ?? ''); + return str_replace('1.7.01.7.0', '1.7.0', $matches[1] ?? ''); + }); } public function netSnmp(): string { - $process = new Process([Config::get('snmpget', 'snmpget'), '-V']); - $process->run(); - preg_match('/[\w.]+$/', $process->getErrorOutput(), $matches); + return $this->cacheGet('net-snmp', function () { + $process = new Process([Config::get('snmpget', 'snmpget'), '-V']); - return $matches[0] ?? ''; + $process->run(); + preg_match('/[\w.]+$/', $process->getErrorOutput(), $matches); + + return $matches[0] ?? ''; + }); } /** @@ -210,30 +229,61 @@ class Version */ public function os(): string { - $info = []; + return $this->cacheGet('os', function () { + $info = []; - // find release file - if (file_exists('/etc/os-release')) { - $info = @parse_ini_file('/etc/os-release'); - } else { - foreach (glob('/etc/*-release') as $file) { - $content = file_get_contents($file); - // normal os release style - $info = @parse_ini_string($content); - if (! empty($info)) { - break; - } + // find release file + if (file_exists('/etc/os-release')) { + $info = @parse_ini_file('/etc/os-release'); + } else { + foreach (glob('/etc/*-release') as $file) { + $content = file_get_contents($file); + // normal os release style + $info = @parse_ini_string($content); + if (! empty($info)) { + break; + } - // just a string of text - if (substr_count($content, PHP_EOL) <= 1) { - $info = ['NAME' => trim(str_replace('release ', '', $content))]; - break; + // just a string of text + if (substr_count($content, PHP_EOL) <= 1) { + $info = ['NAME' => trim(str_replace('release ', '', $content))]; + break; + } } } + + $only = array_intersect_key($info, ['NAME' => true, 'VERSION_ID' => true]); + + return implode(' ', $only); + }); + } + + /** + * We want these each runtime, so don't use the global cache + */ + private function cacheGet(string $name, callable $actual): string + { + if (! array_key_exists($name, $this->cache)) { + $this->cache[$name] = $actual($name); } - $only = array_intersect_key($info, ['NAME' => true, 'VERSION_ID' => true]); + return $this->cache[$name]; + } - return implode(' ', $only); + private function localCommitData(): array + { + return explode('|', $this->cacheGet('local_commit_data', function () { + $install_dir = Config::get('install_dir'); + $version_process = new Process(['git', 'show', '--quiet', '--pretty=%H|%ct'], $install_dir); + $version_process->run(); + + // failed due to permissions issue + if ($version_process->getExitCode() == 128 && Str::startsWith($version_process->getErrorOutput(), 'fatal: unsafe repository')) { + (new Process(['git', 'config', '--global', '--add', 'safe.directory', $install_dir]))->run(); + $version_process->run(); + } + + return rtrim($version_process->getOutput()); + })); } } diff --git a/app/Http/Controllers/AboutController.php b/app/Http/Controllers/AboutController.php index 3591fd4cbd..f02026873b 100644 --- a/app/Http/Controllers/AboutController.php +++ b/app/Http/Controllers/AboutController.php @@ -68,7 +68,7 @@ class AboutController extends Controller 'db_schema' => vsprintf('%s (%s)', $version->database()), 'git_log' => $version->gitChangelog(), - 'git_date' => $version->gitDate(), + 'git_date' => $version->localDate(), 'project_name' => Config::get('project_name'), 'version_local' => $version->local(), diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 0b259f2378..9a792070ef 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -31,6 +31,9 @@ class AppServiceProvider extends ServiceProvider $this->app->singleton('device-cache', function ($app) { return new \LibreNMS\Cache\Device(); }); + $this->app->singleton('version', function ($app) { + return new \LibreNMS\Util\Version(); + }); $this->app->bind(\App\Models\Device::class, function () { /** @var \LibreNMS\Cache\Device $cache */ diff --git a/includes/common.php b/includes/common.php index c544afa6d5..cb8c326f44 100644 --- a/includes/common.php +++ b/includes/common.php @@ -541,12 +541,12 @@ function parse_location($location) function version_info($remote = false) { $version = \LibreNMS\Util\Version::get(); - $local = $version->localCommit(); - $output = [ + + return [ 'local_ver' => $version->local(), - 'local_sha' => $local['sha'], - 'local_date' => $local['date'], - 'local_branch' => $local['branch'], + 'local_sha' => $version->localCommitSha(), + 'local_date' => $version->localDate(), + 'local_branch' => $version->localBranch(), 'github' => $remote ? $version->remoteCommit() : null, 'db_schema' => vsprintf('%s (%s)', $version->database()), 'php_ver' => phpversion(), @@ -555,8 +555,6 @@ function version_info($remote = false) 'rrdtool_ver' => $version->rrdtool(), 'netsnmp_ver' => $version->netSnmp(), ]; - - return $output; }//end version_info() /**