Source of file CacheableNavigation_Rebuild.php
Size: 12,147 Bytes - Last Modified: 2021-12-24T06:44:18+00:00
/var/www/docs.ssmods.com/process/src/code/tasks/CacheableNavigation_Rebuild.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311 | <?php /** * * This BuildTask pre-primes the filesystem or in-memory caches for {@link SiteTree} and * {@link SiteConfig} native SilverStripe objects. * * The BuildTask should be run from the command-line as the webserver user * e.g. www-data otherwise while attempting to access the site from a browser, the * webserver won't have permission to access the cache. E.g: * * <code> * #> sudo -u www-data ./framework/sake dev/tasks/CacheableNavigation_Rebuild * <code> * * You may pass-in an optional "Stage" parameter, with a value of one of "Live" * or "Stage" which helps when debugging or breaking-up the job to make it more * manageable in terms of system resources. It will restrict the cache-rebuild * to objects in the given {@Link Versioned} stage. The default is to cache objects * in both "Stage" and "Live" modes which takes longer to run and uses more memory. * * @author Deviate Ltd 2014-2015 http://www.deviate.net.nz * @package silverstripe-cachable * @see {@link CacheableNavigation_Clean}. * @todo Rename task to better suit the module's new name * @todo Cache filled using {@link Zend_Cache_Core::getFillingPercentage()}. */ class CacheableNavigation_Rebuild extends BuildTask { /** * * A suitable number by which to break-up the total number of pages. * * The idea is to keep this nunmber relatively low, to ensure each chunk as * a QueuedJob is easily managed by PHP's CLI SAPI in terms of memory usage, * especially as there may be 10s of these jobs to be queued. * * @var number */ public static $chunk_divisor = 20; /** * * @var string */ protected $description = 'Rebuilds silverstripe-cacheable object cache.'; /** * * Physically runs the task which - dependent on QueuedJobs being installed and * not skipped via script params - will queue-up chunks of pages to be cached, * or just attempt to cache call objects at once. * * @param SS_HTTPRequest $request * @return void */ public function run($request) { // Increase memory to max-allowable CacheableConfig::configure_memory_limit(); $startTime = time(); $skipQueue = $request->getVar('SkipQueue'); $currentStage = Versioned::current_stage(); /* * Restrict cache rebuild to the given stage - useful for debugging or * "Poor Man's" chunking. */ if ($paramStage = $request->getVar('Stage')) { $stage_mode_mapping = array( ucfirst($paramStage) => strtolower($paramStage) ); // All stages } else { $stage_mode_mapping = array( "Stage" => "Stage", "Live" => "Live", ); } $canQueue = interface_exists('QueuedJob'); $siteConfigs = DataObject::get('SiteConfig'); foreach ($stage_mode_mapping as $stage => $mode) { Versioned::reading_stage($stage); Versioned::set_default_reading_mode('Stage.'.$stage); if (class_exists('Subsite')) { Subsite::disable_subsite_filter(true); Config::inst()->update("CacheableSiteConfig", 'cacheable_fields', array('SubsiteID')); Config::inst()->update("CacheableSiteTree", 'cacheable_fields', array('SubsiteID')); } foreach ($siteConfigs as $config) { $service = new CacheableNavigationService($mode, $config); if ($service->refreshCachedConfig()) { echo 'Caching: SiteConfig object ' . trim($config->ID) . ' (' . $config->Title . ') with mode: ' . $mode . self::new_line(2); } else { echo 'Caching fails: SiteConfig object ' . trim($config->ID) . ' (' . $config->Title . ') with mode: ' . $mode . self::new_line(2); } if (class_exists('Subsite')) { $pages = DataObject::get("Page", "SubsiteID = '" . $config->SubsiteID . "'"); } else { $pages = DataObject::get("Page"); } $pageCount = $pages->count(); /* * * Queueing should only occur if: * - QueuedJob module is available * - SkipQueue param is not set * - Total no. pages is greater than the chunk divisor */ $lowPageCount = (self::$chunk_divisor > $pageCount); $doQueue = ($canQueue && !$skipQueue && !$lowPageCount); if ($pageCount) { $i = 0; $chunkNum = 0; $chunk = array(); foreach ($pages as $page) { $i++; // If QueuedJobs exists and isn't user-disabled: Chunk if ($doQueue) { // Start building a chunk of pages to be refreshed $chunk[] = $page; $chunkSize = count($chunk); /* * Conditions of chunking: * - Initial chunks are chunk-size == self::$chunk_divisor * - Remaining object count equals no. objects in current $chunk */ $doChunking = $this->chunkForQueue($pageCount, $chunkSize, $i); if ($doChunking) { $chunkNum++; $this->queue($service, $chunk, $stage, $config->SubsiteID); echo "Queued chunk #" . $chunkNum . ' (' . $chunkSize . ' objects).' . self::new_line(); $chunk = array(); } // Default to non-chunking if no queuedjobs or script instructed to skip queuing } else { $percentComplete = $this->percentageComplete($i, $pageCount); $service->set_model($page); if ($service->refreshCachedPage(true)) { echo 'Caching: ' . trim($page->Title) . ' (' . $percentComplete . ') ' . self::new_line(); } else { echo 'Caching fails: ' . trim($page->Title) . ' (' . $percentComplete . ') ' . self::new_line(); } } $page->flushCache(); } } $service->completeBuild(); // Completion message $msg = self::new_line() . $pageCount . ' ' . $stage . ' pages in subsite ' . $config->ID; if ($doQueue) { $msg .= ' queued for caching.' . self::new_line(); } else { $msg .= ' objects cached.' . self::new_line(); } echo $msg . self::new_line(); } if (class_exists('Subsite')) { Subsite::disable_subsite_filter(false); } } Versioned::reading_stage($currentStage); Versioned::set_default_reading_mode('Stage.'.$currentStage); $endTime = time(); $totalTime = ($endTime - $startTime); $this->showConfig($totalTime, $request, $lowPageCount); } /** * * Returning boolean true|false this method dictates what gets queued and when. * * @param integer $pageCount * @param integer $chunkSize * @param integer $count * @return boolean */ public function chunkForQueue($pageCount, $chunkSize, $count) { // The no. items-to-cache in full-chunks $totalFullChunkCount = ((int)floor(round($pageCount / self::$chunk_divisor, 1))) * self::$chunk_divisor; $queueFullChunk = ($chunkSize === self::$chunk_divisor); $queuePartChunk = ($count > $totalFullChunkCount && $chunkSize === ($pageCount % self::$chunk_divisor)); return ($queueFullChunk || $queuePartChunk); } /** * * Utility method: Generate a percentage of how complete the cache rebuild is, including * optional memory usage. * * @param number $count * @param number $total * @return string */ private function percentageComplete($count, $total) { $calc = (((int)$count / (int)$total) * 100); return round($calc, 1) . '%'; } /** * * Utility method: Current memory usage in Mb. * * @return number */ private function memory() { return memory_get_peak_usage(true) / 1024 / 1024; } /** * * Utility method: Generate an O/S independent new-line, for as many times * as is required. * * @param number $mul * @return string */ public static function new_line($mul = 1) { $newLine = Director::is_cli() ? PHP_EOL : "<br />"; return str_repeat($newLine, $mul); } /** * * Create a {@link ChunkedCachableRefreshJob} for each "chunk" of N pages * to refresh the caches of. Once run, $chunk is truncated and passed back its * original reference. * * @param CacheableNavigationService $service * @param array $chunk * @param string $stage * @param number $subsiteID * @return number $jobDescriptorID (Return value not used) */ public function queue(CacheableNavigationService $service, $chunk, $stage, $subsiteID) { // We only need to do this during queueing $service->clearInternalCache(); $job = new CachableChunkedRefreshJob($service, $chunk, $stage, $subsiteID); $jobDescriptorID = singleton('QueuedJobService')->queueJob($job); return $jobDescriptorID; } /** * * Summarise the task's configuration details at the end of a run. * * @param string $totalTime * @param SS_HTTPRequest $request * @param boolean $lowCount If the total no. pages is greater than * the chunk divisor, we don't attempt to chunk * and let the user know * @return void */ public function showConfig($totalTime, $request, $lowCount = false) { $skipQueue = ($request->getVar('SkipQueue') && $request->getVar('SkipQueue') == 1); $queueOn = (interface_exists('QueuedJob') ? 'On' : 'Off'); $queueSkipped = ($skipQueue ? ' (Skipped: User instruction)' : ''); if ($lowCount && !$skipQueue) { $queueSkipped = ' (Skipped: Low page count)'; } /** * Is the system underpowered enough such that {@link CacheableConfig::configure_memory_limit()} * has kicked-in? Notify the user accordingly. */ $memMode = CacheableConfig::$ini_modified_memory_limit ? 'Auto-modified' : 'PHP Default'; echo 'Job Queue: ' . $queueOn . $queueSkipped . self::new_line(); echo 'Cache backend: ' . CacheableConfig::current_cache_mode() . self::new_line(); echo 'Peak memory: ' . $this->memory() . 'Mb' . self::new_line(); echo 'Execution time: ' . $totalTime . 's' . self::new_line(); echo 'System memory_limit: ' . ini_get('memory_limit') . ' (' . $memMode . ')' . self::new_line(); echo 'Cache directory: ' . CACHEABLE_STORE_DIR . self::new_line(); } } |