/** * @task config */ protected function establishLiveConnection($mode) { $namespace = self::getStorageNamespace(); $database = $namespace . '_' . $this->getApplicationName(); $is_readonly = PhabricatorEnv::isReadOnly(); if ($is_readonly && $mode != 'r') { $this->raiseImproperWrite($database); } $is_cluster = (bool) PhabricatorEnv::getEnvConfig('cluster.databases'); if ($is_cluster) { $connection = $this->newClusterConnection($database, $mode); } else { $connection = $this->newBasicConnection($database, $mode, $namespace); } // TODO: This should be testing if the mode is "r", but that would proably // break a lot of things. Perform a more narrow test for readonly mode // until we have greater certainty that this works correctly most of the // time. if ($is_readonly) { $connection->setReadOnly(true); } // Unless this is a script running from the CLI, prevent any query from // running for more than 30 seconds. See T10849 for discussion. if (php_sapi_name() != 'cli') { $connection->setQueryTimeout(30); } return $connection; }
/** * @task config */ protected function establishLiveConnection($mode) { $namespace = self::getStorageNamespace(); $database = $namespace . '_' . $this->getApplicationName(); $is_readonly = PhabricatorEnv::isReadOnly(); if ($is_readonly && $mode != 'r') { $this->raiseImproperWrite($database); } $is_cluster = (bool) PhabricatorEnv::getEnvConfig('cluster.databases'); if ($is_cluster) { $connection = $this->newClusterConnection($database, $mode); } else { $connection = $this->newBasicConnection($database, $mode, $namespace); } // TODO: This should be testing if the mode is "r", but that would probably // break a lot of things. Perform a more narrow test for readonly mode // until we have greater certainty that this works correctly most of the // time. if ($is_readonly) { $connection->setReadOnly(true); } // Unless this is a script running from the CLI: // - (T10849) Prevent any query from running for more than 30 seconds. // - (T11672) Use persistent connections. if (php_sapi_name() != 'cli') { // TODO: For now, disable this until after T11044: it's better at high // load, but causes us to use slightly more connections at low load and // is pushing users over limits like MySQL "max_connections". $use_persistent = false; $connection->setQueryTimeout(30)->setPersistent($use_persistent); } return $connection; }
public function setKeys(array $keys, $ttl = null) { if (PhabricatorEnv::isReadOnly()) { return; } if ($keys) { $map = $this->digestKeys(array_keys($keys)); $conn_w = $this->establishConnection('w'); $sql = array(); foreach ($map as $key => $hash) { $value = $keys[$key]; list($format, $storage_value) = $this->willWriteValue($key, $value); $sql[] = qsprintf($conn_w, '(%s, %s, %s, %B, %d, %nd)', $hash, $key, $format, $storage_value, time(), $ttl ? time() + $ttl : null); } $guard = AphrontWriteGuard::beginScopedUnguardedWrites(); foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) { queryfx($conn_w, 'INSERT INTO %T (cacheKeyHash, cacheKey, cacheFormat, cacheData, cacheCreated, cacheExpires) VALUES %Q ON DUPLICATE KEY UPDATE cacheKey = VALUES(cacheKey), cacheFormat = VALUES(cacheFormat), cacheData = VALUES(cacheData), cacheCreated = VALUES(cacheCreated), cacheExpires = VALUES(cacheExpires)', $this->getTableName(), $chunk); } unset($guard); } return $this; }
public static function clearCacheForAllUsers($key) { if (PhabricatorEnv::isReadOnly()) { return; } $table = new self(); $conn_w = $table->establishConnection('w'); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); queryfx($conn_w, 'DELETE FROM %T WHERE cacheIndex = %s', $table->getTableName(), PhabricatorHash::digestForIndex($key)); unset($unguarded); }
public function execute(PhutilArgumentParser $args) { $this->setDryRun($args->getArg('dryrun')); $this->setForce($args->getArg('force')); if (!$this->isReadOnlyWorkflow()) { if (PhabricatorEnv::isReadOnly()) { if ($this->isForce()) { PhabricatorEnv::setReadOnly(false, null); } else { throw new PhutilArgumentUsageException(pht('Phabricator is currently in read-only mode. Use --force to ' . 'override this mode.')); } } } return $this->didExecute($args); }
public static function updateObjectNotificationViews(PhabricatorUser $user, $object_phid) { if (PhabricatorEnv::isReadOnly()) { return; } $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $notification_table = new PhabricatorFeedStoryNotification(); $conn = $notification_table->establishConnection('w'); queryfx($conn, 'UPDATE %T SET hasViewed = 1 WHERE userPHID = %s AND primaryObjectPHID = %s AND hasViewed = 0', $notification_table->getTableName(), $user->getPHID(), $object_phid); unset($unguarded); $count_key = PhabricatorUserNotificationCountCacheType::KEY_COUNT; PhabricatorUserCache::clearCache($count_key, $user->getPHID()); $user->clearCacheData($count_key); }
/** * @task config */ protected function establishLiveConnection($mode) { $namespace = self::getStorageNamespace(); $database = $namespace . '_' . $this->getApplicationName(); $is_readonly = PhabricatorEnv::isReadOnly(); if ($is_readonly && $mode != 'r') { $this->raiseImproperWrite($database); } $connection = $this->newClusterConnection($this->getApplicationName(), $database, $mode); // TODO: This should be testing if the mode is "r", but that would probably // break a lot of things. Perform a more narrow test for readonly mode // until we have greater certainty that this works correctly most of the // time. if ($is_readonly) { $connection->setReadOnly(true); } return $connection; }
protected function willRenderPage() { parent::willRenderPage(); if (!$this->getRequest()) { throw new Exception(pht('You must set the %s to render a %s.', 'Request', __CLASS__)); } $console = $this->getConsole(); require_celerity_resource('phabricator-core-css'); require_celerity_resource('phabricator-zindex-css'); require_celerity_resource('phui-button-css'); require_celerity_resource('phui-spacing-css'); require_celerity_resource('phui-form-css'); require_celerity_resource('phabricator-standard-page-view'); require_celerity_resource('conpherence-durable-column-view'); require_celerity_resource('font-lato'); require_celerity_resource('font-aleo'); Javelin::initBehavior('workflow', array()); $request = $this->getRequest(); $user = null; if ($request) { $user = $request->getUser(); } if ($user) { if ($user->isUserActivated()) { $offset = $user->getTimeZoneOffset(); $ignore_key = PhabricatorTimezoneIgnoreOffsetSetting::SETTINGKEY; $ignore = $user->getUserSetting($ignore_key); Javelin::initBehavior('detect-timezone', array('offset' => $offset, 'uri' => '/settings/timezone/', 'message' => pht('Your browser timezone setting differs from the timezone ' . 'setting in your profile, click to reconcile.'), 'ignoreKey' => $ignore_key, 'ignore' => $ignore)); if ($user->getIsAdmin()) { $server_https = $request->isHTTPS(); $server_protocol = $server_https ? 'HTTPS' : 'HTTP'; $client_protocol = $server_https ? 'HTTP' : 'HTTPS'; $doc_name = 'Configuring a Preamble Script'; $doc_href = PhabricatorEnv::getDoclink($doc_name); Javelin::initBehavior('setup-check-https', array('server_https' => $server_https, 'doc_name' => pht('See Documentation'), 'doc_href' => $doc_href, 'message' => pht('Phabricator thinks you are using %s, but your ' . 'client is conviced that it is using %s. This is a serious ' . 'misconfiguration with subtle, but significant, consequences.', $server_protocol, $client_protocol))); } } $default_img_uri = celerity_get_resource_uri('rsrc/image/icon/fatcow/document_black.png'); $download_form = phabricator_form($user, array('action' => '#', 'method' => 'POST', 'class' => 'lightbox-download-form', 'sigil' => 'download'), phutil_tag('button', array(), pht('Download'))); Javelin::initBehavior('lightbox-attachments', array('defaultImageUri' => $default_img_uri, 'downloadForm' => $download_form)); } Javelin::initBehavior('aphront-form-disable-on-submit'); Javelin::initBehavior('toggle-class', array()); Javelin::initBehavior('history-install'); Javelin::initBehavior('phabricator-gesture'); $current_token = null; if ($user) { $current_token = $user->getCSRFToken(); } Javelin::initBehavior('refresh-csrf', array('tokenName' => AphrontRequest::getCSRFTokenName(), 'header' => AphrontRequest::getCSRFHeaderName(), 'viaHeader' => AphrontRequest::getViaHeaderName(), 'current' => $current_token)); Javelin::initBehavior('device'); Javelin::initBehavior('high-security-warning', $this->getHighSecurityWarningConfig()); if (PhabricatorEnv::isReadOnly()) { Javelin::initBehavior('read-only-warning', array('message' => PhabricatorEnv::getReadOnlyMessage(), 'uri' => PhabricatorEnv::getReadOnlyURI())); } if ($console) { require_celerity_resource('aphront-dark-console-css'); $headers = array(); if (DarkConsoleXHProfPluginAPI::isProfilerStarted()) { $headers[DarkConsoleXHProfPluginAPI::getProfilerHeader()] = 'page'; } if (DarkConsoleServicesPlugin::isQueryAnalyzerRequested()) { $headers[DarkConsoleServicesPlugin::getQueryAnalyzerHeader()] = true; } Javelin::initBehavior('dark-console', $this->getConsoleConfig()); // Change this to initBehavior when there is some behavior to initialize require_celerity_resource('javelin-behavior-error-log'); } if ($user) { $viewer = $user; } else { $viewer = new PhabricatorUser(); } $menu = id(new PhabricatorMainMenuView())->setUser($viewer); if ($this->getController()) { $menu->setController($this->getController()); } $application_menu = $this->getApplicationMenu(); if ($application_menu) { if ($application_menu instanceof PHUIApplicationMenuView) { $crumbs = $this->getCrumbs(); if ($crumbs) { $application_menu->setCrumbs($crumbs); } $application_menu = $application_menu->buildListView(); } $menu->setApplicationMenu($application_menu); } $this->menuContent = $menu->render(); }
private function writeEvents() { if (PhabricatorEnv::isReadOnly()) { return; } $events = $this->events; $random = Filesystem::readRandomBytes(32); $request_key = PhabricatorHash::digestForIndex($random); $host_id = $this->loadHostID(php_uname('n')); $context_id = $this->loadEventContextID($this->eventContext); $viewer_id = $this->loadEventViewerID($this->eventViewer); $label_map = $this->loadEventLabelIDs(mpull($events, 'getEventLabel')); foreach ($events as $event) { $event->setRequestKey($request_key)->setSampleRate($this->sampleRate)->setEventHostID($host_id)->setEventContextID($context_id)->setEventViewerID($viewer_id)->setEventLabelID($label_map[$event->getEventLabel()])->save(); } }
/** * Write to the availability cache. * * @param wild Availability cache data. * @param int|null Cache TTL. * @return this * @task availability */ public function writeAvailabilityCache(array $availability, $ttl) { if (PhabricatorEnv::isReadOnly()) { return $this; } $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); queryfx($this->establishConnection('w'), 'UPDATE %T SET availabilityCache = %s, availabilityCacheTTL = %nd WHERE id = %d', $this->getTableName(), json_encode($availability), $ttl, $this->getID()); unset($unguarded); return $this; }
/** * @task markup */ private function loadPreprocessorCaches(array $engines, array $objects) { $blocks = array(); $use_cache = array(); foreach ($objects as $key => $info) { if ($info['object']->shouldUseMarkupCache($info['field'])) { $use_cache[$key] = true; } } if ($use_cache) { try { $blocks = id(new PhabricatorMarkupCache())->loadAllWhere('cacheKey IN (%Ls)', array_keys($use_cache)); $blocks = mpull($blocks, null, 'getCacheKey'); } catch (Exception $ex) { phlog($ex); } } $is_readonly = PhabricatorEnv::isReadOnly(); foreach ($objects as $key => $info) { // False check in case MySQL doesn't support unicode characters // in the string (T1191), resulting in unserialize returning false. if (isset($blocks[$key]) && $blocks[$key]->getCacheData() !== false) { // If we already have a preprocessing cache, we don't need to rebuild // it. continue; } $text = $info['object']->getMarkupText($info['field']); $data = $engines[$key]->preprocessText($text); // NOTE: This is just debugging information to help sort out cache issues. // If one machine is misconfigured and poisoning caches you can use this // field to hunt it down. $metadata = array('host' => php_uname('n')); $blocks[$key] = id(new PhabricatorMarkupCache())->setCacheKey($key)->setCacheData($data)->setMetadata($metadata); if (isset($use_cache[$key]) && !$is_readonly) { // This is just filling a cache and always safe, even on a read pathway. $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $blocks[$key]->replace(); unset($unguarded); } } return $blocks; }
public function saveCache() { if (PhabricatorEnv::isReadOnly()) { return false; } if ($this->highlightErrors) { return false; } $render_cache_key = $this->getRenderCacheKey(); if (!$render_cache_key) { return false; } $cache = array(); foreach (self::getCacheableProperties() as $cache_key) { switch ($cache_key) { case 'cacheVersion': $cache[$cache_key] = self::CACHE_VERSION; break; case 'cacheHost': $cache[$cache_key] = php_uname('n'); break; default: $cache[$cache_key] = $this->{$cache_key}; break; } } $cache = serialize($cache); // We don't want to waste too much space by a single changeset. if (strlen($cache) > self::CACHE_MAX_SIZE) { return; } $changeset = new DifferentialChangeset(); $conn_w = $changeset->establishConnection('w'); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); try { queryfx($conn_w, 'INSERT INTO %T (id, cache, dateCreated) VALUES (%d, %B, %d) ON DUPLICATE KEY UPDATE cache = VALUES(cache)', DifferentialChangeset::TABLE_CACHE, $render_cache_key, $cache, time()); } catch (AphrontQueryException $ex) { // Ignore these exceptions. A common cause is that the cache is // larger than 'max_allowed_packet', in which case we're better off // not writing it. // TODO: It would be nice to tailor this more narrowly. } unset($unguarded); }
protected function executeChecks() { // TODO: These checks should be executed against every reachable replica? // See T10759. if (PhabricatorEnv::isReadOnly()) { return; } $max_allowed_packet = self::loadRawConfigValue('max_allowed_packet'); // This primarily supports setting the filesize limit for MySQL to 8MB, // which may produce a >16MB packet after escaping. $recommended_minimum = 32 * 1024 * 1024; if ($max_allowed_packet < $recommended_minimum) { $message = pht("MySQL is configured with a small '%s' (%d), " . "which may cause some large writes to fail. Strongly consider raising " . "this to at least %d in your MySQL configuration.", 'max_allowed_packet', $max_allowed_packet, $recommended_minimum); $this->newIssue('mysql.max_allowed_packet')->setName(pht('Small MySQL "%s"', 'max_allowed_packet'))->setMessage($message)->addMySQLConfig('max_allowed_packet'); } $modes = self::loadRawConfigValue('sql_mode'); $modes = explode(',', $modes); if (!in_array('STRICT_ALL_TABLES', $modes)) { $summary = pht('MySQL is not in strict mode, but using strict mode is strongly ' . 'encouraged.'); $message = pht("On your MySQL instance, the global %s is not set to %s. " . "It is strongly encouraged that you enable this mode when running " . "Phabricator.\n\n" . "By default MySQL will silently ignore some types of errors, which " . "can cause data loss and raise security concerns. Enabling strict " . "mode makes MySQL raise an explicit error instead, and prevents this " . "entire class of problems from doing any damage.\n\n" . "You can find more information about this mode (and how to configure " . "it) in the MySQL manual. Usually, it is sufficient to add this to " . "your %s file (in the %s section) and then restart %s:\n\n" . "%s\n" . "(Note that if you run other applications against the same database, " . "they may not work in strict mode. Be careful about enabling it in " . "these cases.)", phutil_tag('tt', array(), 'sql_mode'), phutil_tag('tt', array(), 'STRICT_ALL_TABLES'), phutil_tag('tt', array(), 'my.cnf'), phutil_tag('tt', array(), '[mysqld]'), phutil_tag('tt', array(), 'mysqld'), phutil_tag('pre', array(), 'sql_mode=STRICT_ALL_TABLES')); $this->newIssue('mysql.mode')->setName(pht('MySQL %s Mode Not Set', 'STRICT_ALL_TABLES'))->setSummary($summary)->setMessage($message)->addMySQLConfig('sql_mode'); } if (in_array('ONLY_FULL_GROUP_BY', $modes)) { $summary = pht('MySQL is in ONLY_FULL_GROUP_BY mode, but using this mode is strongly ' . 'discouraged.'); $message = pht("On your MySQL instance, the global %s is set to %s. " . "It is strongly encouraged that you disable this mode when running " . "Phabricator.\n\n" . "With %s enabled, MySQL rejects queries for which the select list " . "or (as of MySQL 5.0.23) %s list refer to nonaggregated columns " . "that are not named in the %s clause. More importantly, Phabricator " . "does not work properly with this mode enabled.\n\n" . "You can find more information about this mode (and how to configure " . "it) in the MySQL manual. Usually, it is sufficient to change the %s " . "in your %s file (in the %s section) and then restart %s:\n\n" . "%s\n" . "(Note that if you run other applications against the same database, " . "they may not work with %s. Be careful about enabling " . "it in these cases and consider migrating Phabricator to a different " . "database.)", phutil_tag('tt', array(), 'sql_mode'), phutil_tag('tt', array(), 'ONLY_FULL_GROUP_BY'), phutil_tag('tt', array(), 'ONLY_FULL_GROUP_BY'), phutil_tag('tt', array(), 'HAVING'), phutil_tag('tt', array(), 'GROUP BY'), phutil_tag('tt', array(), 'sql_mode'), phutil_tag('tt', array(), 'my.cnf'), phutil_tag('tt', array(), '[mysqld]'), phutil_tag('tt', array(), 'mysqld'), phutil_tag('pre', array(), 'sql_mode=STRICT_ALL_TABLES'), phutil_tag('tt', array(), 'ONLY_FULL_GROUP_BY')); $this->newIssue('mysql.mode')->setName(pht('MySQL %s Mode Set', 'ONLY_FULL_GROUP_BY'))->setSummary($summary)->setMessage($message)->addMySQLConfig('sql_mode'); } $stopword_file = self::loadRawConfigValue('ft_stopword_file'); if ($this->shouldUseMySQLSearchEngine()) { if ($stopword_file === null) { $summary = pht('Your version of MySQL does not support configuration of a ' . 'stopword file. You will not be able to find search results for ' . 'common words.'); $message = pht("Your MySQL instance does not support the %s option. You will not " . "be able to find search results for common words. You can gain " . "access to this option by upgrading MySQL to a more recent " . "version.\n\n" . "You can ignore this warning if you plan to configure ElasticSearch " . "later, or aren't concerned about searching for common words.", phutil_tag('tt', array(), 'ft_stopword_file')); $this->newIssue('mysql.ft_stopword_file')->setName(pht('MySQL %s Not Supported', 'ft_stopword_file'))->setSummary($summary)->setMessage($message)->addMySQLConfig('ft_stopword_file'); } else { if ($stopword_file == '(built-in)') { $root = dirname(phutil_get_library_root('phabricator')); $stopword_path = $root . '/resources/sql/stopwords.txt'; $stopword_path = Filesystem::resolvePath($stopword_path); $namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace'); $summary = pht('MySQL is using a default stopword file, which will prevent ' . 'searching for many common words.'); $message = pht("Your MySQL instance is using the builtin stopword file for " . "building search indexes. This can make Phabricator's search " . "feature less useful.\n\n" . "Stopwords are common words which are not indexed and thus can not " . "be searched for. The default stopword file has about 500 words, " . "including various words which you are likely to wish to search " . "for, such as 'various', 'likely', 'wish', and 'zero'.\n\n" . "To make search more useful, you can use an alternate stopword " . "file with fewer words. Alternatively, if you aren't concerned " . "about searching for common words, you can ignore this warning. " . "If you later plan to configure ElasticSearch, you can also ignore " . "this warning: this stopword file only affects MySQL fulltext " . "indexes.\n\n" . "To choose a different stopword file, add this to your %s file " . "(in the %s section) and then restart %s:\n\n" . "%s\n" . "(You can also use a different file if you prefer. The file " . "suggested above has about 50 of the most common English words.)\n\n" . "Finally, run this command to rebuild indexes using the new " . "rules:\n\n" . "%s", phutil_tag('tt', array(), 'my.cnf'), phutil_tag('tt', array(), '[mysqld]'), phutil_tag('tt', array(), 'mysqld'), phutil_tag('pre', array(), 'ft_stopword_file=' . $stopword_path), phutil_tag('pre', array(), "mysql> REPAIR TABLE {$namespace}_search.search_documentfield;")); $this->newIssue('mysql.ft_stopword_file')->setName(pht('MySQL is Using Default Stopword File'))->setSummary($summary)->setMessage($message)->addMySQLConfig('ft_stopword_file'); } } } $min_len = self::loadRawConfigValue('ft_min_word_len'); if ($min_len >= 4) { if ($this->shouldUseMySQLSearchEngine()) { $namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace'); $summary = pht('MySQL is configured to only index words with at least %d ' . 'characters.', $min_len); $message = pht("Your MySQL instance is configured to use the default minimum word " . "length when building search indexes, which is 4. This means words " . "which are only 3 characters long will not be indexed and can not " . "be searched for.\n\n" . "For example, you will not be able to find search results for words " . "like 'SMS', 'web', or 'DOS'.\n\n" . "You can change this setting to 3 to allow these words to be " . "indexed. Alternatively, you can ignore this warning if you are " . "not concerned about searching for 3-letter words. If you later " . "plan to configure ElasticSearch, you can also ignore this warning: " . "only MySQL fulltext search is affected.\n\n" . "To reduce the minimum word length to 3, add this to your %s file " . "(in the %s section) and then restart %s:\n\n" . "%s\n" . "Finally, run this command to rebuild indexes using the new " . "rules:\n\n" . "%s", phutil_tag('tt', array(), 'my.cnf'), phutil_tag('tt', array(), '[mysqld]'), phutil_tag('tt', array(), 'mysqld'), phutil_tag('pre', array(), 'ft_min_word_len=3'), phutil_tag('pre', array(), "mysql> REPAIR TABLE {$namespace}_search.search_documentfield;")); $this->newIssue('mysql.ft_min_word_len')->setName(pht('MySQL is Using Default Minimum Word Length'))->setSummary($summary)->setMessage($message)->addMySQLConfig('ft_min_word_len'); } } $bool_syntax = self::loadRawConfigValue('ft_boolean_syntax'); if ($bool_syntax != ' |-><()~*:""&^') { if ($this->shouldUseMySQLSearchEngine()) { $summary = pht('MySQL is configured to search on fulltext indexes using "OR" by ' . 'default. Using "AND" is usually the desired behaviour.'); $message = pht("Your MySQL instance is configured to use the default Boolean " . "search syntax when using fulltext indexes. This means searching " . "for 'search words' will yield the query 'search OR words' " . "instead of the desired 'search AND words'.\n\n" . "This might produce unexpected search results. \n\n" . "You can change this setting to a more sensible default. " . "Alternatively, you can ignore this warning if " . "using 'OR' is the desired behaviour. If you later plan " . "to configure ElasticSearch, you can also ignore this warning: " . "only MySQL fulltext search is affected.\n\n" . "To change this setting, add this to your %s file " . "(in the %s section) and then restart %s:\n\n" . "%s\n", phutil_tag('tt', array(), 'my.cnf'), phutil_tag('tt', array(), '[mysqld]'), phutil_tag('tt', array(), 'mysqld'), phutil_tag('pre', array(), 'ft_boolean_syntax=\' |-><()~*:""&^\'')); $this->newIssue('mysql.ft_boolean_syntax')->setName(pht('MySQL is Using the Default Boolean Syntax'))->setSummary($summary)->setMessage($message)->addMySQLConfig('ft_boolean_syntax'); } } $innodb_pool = self::loadRawConfigValue('innodb_buffer_pool_size'); $innodb_bytes = phutil_parse_bytes($innodb_pool); $innodb_readable = phutil_format_bytes($innodb_bytes); // This is arbitrary and just trying to detect values that the user // probably didn't set themselves. The Mac OS X default is 128MB and // 40% of an AWS EC2 Micro instance is 245MB, so keeping it somewhere // between those two values seems like a reasonable approximation. $minimum_readable = '225MB'; $minimum_bytes = phutil_parse_bytes($minimum_readable); if ($innodb_bytes < $minimum_bytes) { $summary = pht('MySQL is configured with a very small innodb_buffer_pool_size, ' . 'which may impact performance.'); $message = pht("Your MySQL instance is configured with a very small %s (%s). " . "This may cause poor database performance and lock exhaustion.\n\n" . "There are no hard-and-fast rules to setting an appropriate value, " . "but a reasonable starting point for a standard install is something " . "like 40%% of the total memory on the machine. For example, if you " . "have 4GB of RAM on the machine you have installed Phabricator on, " . "you might set this value to %s.\n\n" . "You can read more about this option in the MySQL documentation to " . "help you make a decision about how to configure it for your use " . "case. There are no concerns specific to Phabricator which make it " . "different from normal workloads with respect to this setting.\n\n" . "To adjust the setting, add something like this to your %s file (in " . "the %s section), replacing %s with an appropriate value for your " . "host and use case. Then restart %s:\n\n" . "%s\n" . "If you're satisfied with the current setting, you can safely " . "ignore this setup warning.", phutil_tag('tt', array(), 'innodb_buffer_pool_size'), phutil_tag('tt', array(), $innodb_readable), phutil_tag('tt', array(), '1600M'), phutil_tag('tt', array(), 'my.cnf'), phutil_tag('tt', array(), '[mysqld]'), phutil_tag('tt', array(), '1600M'), phutil_tag('tt', array(), 'mysqld'), phutil_tag('pre', array(), 'innodb_buffer_pool_size=1600M')); $this->newIssue('mysql.innodb_buffer_pool_size')->setName(pht('MySQL May Run Slowly'))->setSummary($summary)->setMessage($message)->addMySQLConfig('innodb_buffer_pool_size'); } $conn_w = id(new PhabricatorUser())->establishConnection('w'); $ok = PhabricatorStorageManagementAPI::isCharacterSetAvailableOnConnection('utf8mb4', $conn_w); if (!$ok) { $summary = pht('You are using an old version of MySQL, and should upgrade.'); $message = pht('You are using an old version of MySQL which has poor unicode ' . 'support (it does not support the "utf8mb4" collation set). You will ' . 'encounter limitations when working with some unicode data.' . "\n\n" . 'We strongly recommend you upgrade to MySQL 5.5 or newer.'); $this->newIssue('mysql.utf8mb4')->setName(pht('Old MySQL Version'))->setSummary($summary)->setMessage($message); } $info = queryfx_one($conn_w, 'SELECT UNIX_TIMESTAMP() epoch'); $epoch = (int) $info['epoch']; $local = PhabricatorTime::getNow(); $delta = (int) abs($local - $epoch); if ($delta > 60) { $this->newIssue('mysql.clock')->setName(pht('Major Web/Database Clock Skew'))->setSummary(pht('This host is set to a very different time than the database.'))->setMessage(pht('The database host and this host ("%s") disagree on the current ' . 'time by more than 60 seconds (absolute skew is %s seconds). ' . 'Check that the current time is set correctly everywhere.', php_uname('n'), new PhutilNumber($delta))); } }
private function buildClusterStatusPanel() { $repository = $this->getRepository(); $viewer = $this->getViewer(); $service_phid = $repository->getAlmanacServicePHID(); if ($service_phid) { $service = id(new AlmanacServiceQuery())->setViewer($viewer)->withServiceTypes(array(AlmanacClusterRepositoryServiceType::SERVICETYPE))->withPHIDs(array($service_phid))->needBindings(true)->executeOne(); if (!$service) { // TODO: Viewer may not have permission to see the service, or it may // be invalid? Raise some more useful error here? throw new Exception(pht('Unable to load cluster service.')); } } else { $service = null; } Javelin::initBehavior('phabricator-tooltips'); $rows = array(); if ($service) { $bindings = $service->getBindings(); $bindings = mgroup($bindings, 'getDevicePHID'); // This is an unusual read which always comes from the master. if (PhabricatorEnv::isReadOnly()) { $versions = array(); } else { $versions = PhabricatorRepositoryWorkingCopyVersion::loadVersions($repository->getPHID()); } $versions = mpull($versions, null, 'getDevicePHID'); foreach ($bindings as $binding_group) { $all_disabled = true; foreach ($binding_group as $binding) { if (!$binding->getIsDisabled()) { $all_disabled = false; break; } } $any_binding = head($binding_group); if ($all_disabled) { $binding_icon = 'fa-times grey'; $binding_tip = pht('Disabled'); } else { $binding_icon = 'fa-folder-open green'; $binding_tip = pht('Active'); } $binding_icon = id(new PHUIIconView())->setIcon($binding_icon)->addSigil('has-tooltip')->setMetadata(array('tip' => $binding_tip)); $device = $any_binding->getDevice(); $version = idx($versions, $device->getPHID()); if ($version) { $version_number = $version->getRepositoryVersion(); $href = null; if ($repository->isHosted()) { $href = "/diffusion/pushlog/view/{$version_number}/"; } else { $commit = id(new DiffusionCommitQuery())->setViewer($viewer)->withIDs(array($version_number))->executeOne(); if ($commit) { $href = $commit->getURI(); } } if ($href) { $version_number = phutil_tag('a', array('href' => $href), $version_number); } } else { $version_number = '-'; } if ($version && $version->getIsWriting()) { $is_writing = id(new PHUIIconView())->setIcon('fa-pencil green'); } else { $is_writing = id(new PHUIIconView())->setIcon('fa-pencil grey'); } $write_properties = null; if ($version) { $write_properties = $version->getWriteProperties(); if ($write_properties) { try { $write_properties = phutil_json_decode($write_properties); } catch (Exception $ex) { $write_properties = null; } } } if ($write_properties) { $writer_phid = idx($write_properties, 'userPHID'); $last_writer = $viewer->renderHandle($writer_phid); $writer_epoch = idx($write_properties, 'epoch'); $writer_epoch = phabricator_datetime($writer_epoch, $viewer); } else { $last_writer = null; $writer_epoch = null; } $rows[] = array($binding_icon, phutil_tag('a', array('href' => $device->getURI()), $device->getName()), $version_number, $is_writing, $last_writer, $writer_epoch); } } $table = id(new AphrontTableView($rows))->setNoDataString(pht('This is not a cluster repository.'))->setHeaders(array(null, pht('Device'), pht('Version'), pht('Writing'), pht('Last Writer'), pht('Last Write At')))->setColumnClasses(array(null, null, null, 'right wide', null, 'date')); $doc_href = PhabricatorEnv::getDoclink('Cluster: Repositories'); $header = id(new PHUIHeaderView())->setHeader(pht('Cluster Status'))->addActionLink(id(new PHUIButtonView())->setIcon('fa-book')->setHref($doc_href)->setTag('a')->setText(pht('Documentation'))); return id(new PHUIObjectBoxView())->setHeader($header)->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)->setTable($table); }