@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'master' into redesign-2015

+1408 -336
+2
src/__phutil_library_map__.php
··· 652 652 'DivinerAtomizeWorkflow' => 'applications/diviner/workflow/DivinerAtomizeWorkflow.php', 653 653 'DivinerAtomizer' => 'applications/diviner/atomizer/DivinerAtomizer.php', 654 654 'DivinerBookController' => 'applications/diviner/controller/DivinerBookController.php', 655 + 'DivinerBookDatasource' => 'applications/diviner/typeahead/DivinerBookDatasource.php', 655 656 'DivinerBookEditController' => 'applications/diviner/controller/DivinerBookEditController.php', 656 657 'DivinerBookItemView' => 'applications/diviner/view/DivinerBookItemView.php', 657 658 'DivinerBookPHIDType' => 'applications/diviner/phid/DivinerBookPHIDType.php', ··· 4021 4022 'DivinerAtomizeWorkflow' => 'DivinerWorkflow', 4022 4023 'DivinerAtomizer' => 'Phobject', 4023 4024 'DivinerBookController' => 'DivinerController', 4025 + 'DivinerBookDatasource' => 'PhabricatorTypeaheadDatasource', 4024 4026 'DivinerBookEditController' => 'DivinerController', 4025 4027 'DivinerBookItemView' => 'AphrontTagView', 4026 4028 'DivinerBookPHIDType' => 'PhabricatorPHIDType',
+44
src/applications/diffusion/query/DiffusionCommitQuery.php
··· 17 17 private $auditorPHIDs; 18 18 private $auditAwaitingUser; 19 19 private $auditStatus; 20 + private $epochMin; 21 + private $epochMax; 22 + private $importing; 20 23 21 24 const AUDIT_STATUS_ANY = 'audit-status-any'; 22 25 const AUDIT_STATUS_OPEN = 'audit-status-open'; ··· 138 141 139 142 public function withAuditStatus($status) { 140 143 $this->auditStatus = $status; 144 + return $this; 145 + } 146 + 147 + public function withEpochRange($min, $max) { 148 + $this->epochMin = $min; 149 + $this->epochMax = $max; 150 + return $this; 151 + } 152 + 153 + public function withImporting($importing) { 154 + $this->importing = $importing; 141 155 return $this; 142 156 } 143 157 ··· 327 341 $conn_r, 328 342 'commit.authorPHID IN (%Ls)', 329 343 $this->authorPHIDs); 344 + } 345 + 346 + if ($this->epochMin !== null) { 347 + $where[] = qsprintf( 348 + $conn_r, 349 + 'commit.epoch >= %d', 350 + $this->epochMin); 351 + } 352 + 353 + if ($this->epochMax !== null) { 354 + $where[] = qsprintf( 355 + $conn_r, 356 + 'commit.epoch <= %d', 357 + $this->epochMax); 358 + } 359 + 360 + if ($this->importing !== null) { 361 + if ($this->importing) { 362 + $where[] = qsprintf( 363 + $conn_r, 364 + '(commit.importStatus & %d) != %d', 365 + PhabricatorRepositoryCommit::IMPORTED_ALL, 366 + PhabricatorRepositoryCommit::IMPORTED_ALL); 367 + } else { 368 + $where[] = qsprintf( 369 + $conn_r, 370 + '(commit.importStatus & %d) = %d', 371 + PhabricatorRepositoryCommit::IMPORTED_ALL, 372 + PhabricatorRepositoryCommit::IMPORTED_ALL); 373 + } 330 374 } 331 375 332 376 if ($this->identifiers !== null) {
+15
src/applications/diviner/query/DivinerAtomSearchEngine.php
··· 14 14 $saved = new PhabricatorSavedQuery(); 15 15 16 16 $saved->setParameter( 17 + 'bookPHIDs', 18 + $this->readPHIDsFromRequest($request, 'bookPHIDs')); 19 + $saved->setParameter( 17 20 'repositoryPHIDs', 18 21 $this->readPHIDsFromRequest($request, 'repositoryPHIDs')); 19 22 $saved->setParameter('name', $request->getStr('name')); ··· 26 29 27 30 public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { 28 31 $query = id(new DivinerAtomQuery()); 32 + 33 + $books = $saved->getParameter('bookPHIDs'); 34 + if ($books) { 35 + $query->withBookPHIDs($books); 36 + } 29 37 30 38 $repository_phids = $saved->getParameter('repositoryPHIDs'); 31 39 if ($repository_phids) { ··· 73 81 isset($types[$type])); 74 82 } 75 83 $form->appendChild($type_control); 84 + 85 + $form->appendControl( 86 + id(new AphrontFormTokenizerControl()) 87 + ->setDatasource(new DivinerBookDatasource()) 88 + ->setName('bookPHIDs') 89 + ->setLabel(pht('Books')) 90 + ->setValue($saved->getParameter('bookPHIDs'))); 76 91 77 92 $form->appendControl( 78 93 id(new AphrontFormTokenizerControl())
+55 -1
src/applications/diviner/query/DivinerBookQuery.php
··· 5 5 private $ids; 6 6 private $phids; 7 7 private $names; 8 + private $nameLike; 9 + private $namePrefix; 8 10 private $repositoryPHIDs; 9 11 10 12 private $needProjectPHIDs; ··· 20 22 return $this; 21 23 } 22 24 25 + public function withNameLike($name) { 26 + $this->nameLike = $name; 27 + return $this; 28 + } 29 + 23 30 public function withNames(array $names) { 24 31 $this->names = $names; 32 + return $this; 33 + } 34 + 35 + public function withNamePrefix($prefix) { 36 + $this->namePrefix = $prefix; 25 37 return $this; 26 38 } 27 39 ··· 121 133 $this->phids); 122 134 } 123 135 124 - if ($this->names) { 136 + if (strlen($this->nameLike)) { 137 + $where[] = qsprintf( 138 + $conn_r, 139 + 'name LIKE %~', 140 + $this->nameLike); 141 + } 142 + 143 + if ($this->names !== null) { 125 144 $where[] = qsprintf( 126 145 $conn_r, 127 146 'name IN (%Ls)', 128 147 $this->names); 129 148 } 130 149 150 + if (strlen($this->namePrefix)) { 151 + $where[] = qsprintf( 152 + $conn_r, 153 + 'name LIKE %>', 154 + $this->namePrefix); 155 + } 156 + 131 157 if ($this->repositoryPHIDs !== null) { 132 158 $where[] = qsprintf( 133 159 $conn_r, ··· 142 168 143 169 public function getQueryApplicationClass() { 144 170 return 'PhabricatorDivinerApplication'; 171 + } 172 + 173 + public function getOrderableColumns() { 174 + return parent::getOrderableColumns() + array( 175 + 'name' => array( 176 + 'column' => 'name', 177 + 'type' => 'string', 178 + 'reverse' => true, 179 + 'unique' => true, 180 + ), 181 + ); 182 + } 183 + 184 + protected function getPagingValueMap($cursor, array $keys) { 185 + $book = $this->loadCursorObject($cursor); 186 + 187 + return array( 188 + 'name' => $book->getName(), 189 + ); 190 + } 191 + 192 + public function getBuiltinOrders() { 193 + return array( 194 + 'name' => array( 195 + 'vector' => array('name'), 196 + 'name' => pht('Name'), 197 + ), 198 + ) + parent::getBuiltinOrders(); 145 199 } 146 200 147 201 }
+37
src/applications/diviner/typeahead/DivinerBookDatasource.php
··· 1 + <?php 2 + 3 + final class DivinerBookDatasource extends PhabricatorTypeaheadDatasource { 4 + 5 + public function getBrowseTitle() { 6 + return pht('Browse Books'); 7 + } 8 + 9 + public function getPlaceholderText() { 10 + return pht('Type a book name...'); 11 + } 12 + 13 + public function getDatasourceApplicationClass() { 14 + return 'PhabricatorDivinerApplication'; 15 + } 16 + 17 + public function loadResults() { 18 + $raw_query = $this->getRawQuery(); 19 + 20 + $query = id(new DivinerBookQuery()) 21 + ->setOrder('name') 22 + ->withNamePrefix($raw_query); 23 + $books = $this->executeQuery($query); 24 + 25 + $results = array(); 26 + foreach ($books as $book) { 27 + $results[] = id(new PhabricatorTypeaheadResult()) 28 + ->setName($book->getTitle()) 29 + ->setURI('/book/'.$book->getName().'/') 30 + ->setPHID($book->getPHID()) 31 + ->setPriorityString($book->getName()); 32 + } 33 + 34 + return $results; 35 + } 36 + 37 + }
+2 -2
src/applications/harbormaster/controller/HarbormasterPlanViewController.php
··· 27 27 new HarbormasterBuildPlanTransactionQuery()); 28 28 $timeline->setShouldTerminate(true); 29 29 30 - $title = pht('Plan %d', $id); 30 + $title = $plan->getName(); 31 31 32 32 $header = id(new PHUIHeaderView()) 33 - ->setHeader($title) 33 + ->setHeader($plan->getName()) 34 34 ->setUser($viewer) 35 35 ->setPolicyObject($plan); 36 36
+9
src/applications/multimeter/application/PhabricatorMultimeterApplication.php
··· 43 43 ); 44 44 } 45 45 46 + public function getHelpDocumentationArticles(PhabricatorUser $viewer) { 47 + return array( 48 + array( 49 + 'name' => pht('Multimeter User Guide'), 50 + 'href' => PhabricatorEnv::getDoclink('Multimeter User Guide'), 51 + ), 52 + ); 53 + } 54 + 46 55 }
+70 -24
src/applications/repository/management/PhabricatorRepositoryManagementReparseWorkflow.php
··· 84 84 '--all'), 85 85 ), 86 86 array( 87 + 'name' => 'importing', 88 + 'help' => pht( 89 + 'Reparse all steps which have not yet completed.'), 90 + ), 91 + array( 87 92 'name' => 'force-autoclose', 88 93 'help' => pht( 89 - 'Only used with __%s, use this to make sure any '. 94 + 'Only used with __%s__, use this to make sure any '. 90 95 'pertinent diffs are closed regardless of configuration.', 91 - '--message__'), 96 + '--message'), 92 97 ), 93 98 )); 94 99 ··· 106 111 $force = $args->getArg('force'); 107 112 $force_local = $args->getArg('force-local'); 108 113 $min_date = $args->getArg('min-date'); 114 + $importing = $args->getArg('importing'); 109 115 110 116 if (!$all_from_repo && !$reparse_what) { 111 117 throw new PhutilArgumentUsageException( ··· 123 129 $commits)); 124 130 } 125 131 126 - if (!$reparse_message && !$reparse_change && !$reparse_herald && 127 - !$reparse_owners) { 132 + $any_step = ($reparse_message || 133 + $reparse_change || 134 + $reparse_herald || 135 + $reparse_owners); 136 + 137 + if ($any_step && $importing) { 138 + throw new PhutilArgumentUsageException( 139 + pht( 140 + 'Choosing steps with %s conflicts with flags which select '. 141 + 'specific steps.', 142 + '--importing')); 143 + } else if ($any_step) { 144 + // OK. 145 + } else if ($importing) { 146 + // OK. 147 + } else if (!$any_step && !$importing) { 128 148 throw new PhutilArgumentUsageException( 129 149 pht( 130 - 'Specify what information to reparse with %s, %s, %s, and/or %s.', 150 + 'Specify which steps to reparse with %s, or %s, %s, %s, or %s.', 151 + '--importing', 131 152 '--message', 132 153 '--change', 133 154 '--herald', 134 155 '--owners')); 135 - } 156 + } 136 157 137 158 $min_timestamp = false; 138 159 if ($min_date) { ··· 179 200 throw new PhutilArgumentUsageException( 180 201 pht('Unknown repository %s!', $all_from_repo)); 181 202 } 182 - $constraint = ''; 203 + 204 + 205 + $query = id(new DiffusionCommitQuery()) 206 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 207 + ->withRepository($repository); 208 + 183 209 if ($min_timestamp) { 184 - $console->writeOut("%s\n", pht( 185 - 'Excluding entries before UNIX timestamp: %s', 186 - $min_timestamp)); 187 - $table = new PhabricatorRepositoryCommit(); 188 - $conn_r = $table->establishConnection('r'); 189 - $constraint = qsprintf( 190 - $conn_r, 191 - 'AND epoch >= %d', 192 - $min_timestamp); 210 + $query->withEpochRange($min_timestamp, null); 211 + } 212 + 213 + if ($importing) { 214 + $query->withImporting(true); 193 215 } 194 - $commits = id(new PhabricatorRepositoryCommit())->loadAllWhere( 195 - 'repositoryID = %d %Q', 196 - $repository->getID(), 197 - $constraint); 216 + 217 + $commits = $query->execute(); 218 + 198 219 $callsign = $repository->getCallsign(); 199 220 if (!$commits) { 200 - throw new PhutilArgumentUsageException(pht( 201 - "No commits have been discovered in %s repository!\n", 202 - $callsign)); 221 + throw new PhutilArgumentUsageException( 222 + pht( 223 + 'No commits have been discovered in %s repository!', 224 + $callsign)); 203 225 } 204 226 } else { 205 227 $commits = array(); ··· 250 272 251 273 $tasks = array(); 252 274 foreach ($commits as $commit) { 275 + if ($importing) { 276 + $status = $commit->getImportStatus(); 277 + // Find the first missing import step and queue that up. 278 + $reparse_message = false; 279 + $reparse_change = false; 280 + $reparse_owners = false; 281 + $reparse_herald = false; 282 + if (!($status & PhabricatorRepositoryCommit::IMPORTED_MESSAGE)) { 283 + $reparse_message = true; 284 + } else if (!($status & PhabricatorRepositoryCommit::IMPORTED_CHANGE)) { 285 + $reparse_change = true; 286 + } else if (!($status & PhabricatorRepositoryCommit::IMPORTED_OWNERS)) { 287 + $reparse_owners = true; 288 + } else if (!($status & PhabricatorRepositoryCommit::IMPORTED_HERALD)) { 289 + $reparse_herald = true; 290 + } else { 291 + continue; 292 + } 293 + } 294 + 253 295 $classes = array(); 254 296 switch ($repository->getVersionControlSystem()) { 255 297 case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: ··· 287 329 $classes[] = 'PhabricatorRepositoryCommitOwnersWorker'; 288 330 } 289 331 332 + // NOTE: With "--importing", we queue the first unparsed step and let 333 + // it queue the other ones normally. Without "--importing", we queue 334 + // all the requested steps explicitly. 335 + 290 336 $spec = array( 291 337 'commitID' => $commit->getID(), 292 - 'only' => true, 338 + 'only' => !$importing, 293 339 'forceAutoclose' => $args->getArg('force-autoclose'), 294 340 ); 295 341
+1 -1
src/docs/book/contributor.book
··· 2 2 "name": "phabcontrib", 3 3 "title": "Phabricator Contributor Documentation", 4 4 "short": "Phabricator Contributor Docs", 5 - "preface": "Information for Phabricator contributors.", 5 + "preface": "Information for Phabricator contributors and developers.", 6 6 "root": "../../../", 7 7 "uri.source": 8 8 "https://secure.phabricator.com/diffusion/P/browse/master/%f$%l",
+1 -1
src/docs/book/phabricator.book
··· 2 2 "name": "phabdev", 3 3 "title": "Phabricator Technical Documentation", 4 4 "short": "Phabricator Tech Docs", 5 - "preface": "Technical documentation intended for Phabricator developers.", 5 + "preface": "Technical reference material for Phabricator developers.", 6 6 "root": "../../../", 7 7 "uri.source": 8 8 "https://secure.phabricator.com/diffusion/P/browse/master/%f$%l",
+3
src/docs/book/user.book
··· 28 28 }, 29 29 "userguide": { 30 30 "name": "Application User Guides" 31 + }, 32 + "fieldmanual": { 33 + "name": "Field Manuals" 31 34 } 32 35 } 33 36 }
+256
src/docs/contributor/adding_new_classes.diviner
··· 1 + @title Adding New Classes 2 + @group developer 3 + 4 + Guide to adding new classes to extend Phabricator. 5 + 6 + Overview 7 + ======== 8 + 9 + Phabricator is highly modular, and many parts of it can be extended by adding 10 + new classes. This document explains how to write new classes to change or 11 + expand the behavior of Phabricator. 12 + 13 + IMPORTANT: The upstream does not offer support with extension development. 14 + 15 + Fundamentals 16 + ============ 17 + 18 + Phabricator primarily discovers functionality by looking at concrete subclasses 19 + of some base class. For example, Phabricator determines which applications are 20 + available by looking at all of the subclasses of 21 + @{class@phabricator:PhabricatorApplication}. It 22 + discovers available workflows in `arc` by looking at all of the subclasses of 23 + @{class@arcanist:ArcanistWorkflow}. It discovers available locales 24 + by looking at all of the subclasses of @{class@libphutil:PhutilLocale}. 25 + 26 + This pattern holds in many cases, so you can often add functionality by adding 27 + new classes with no other work. Phabricator will automatically discover and 28 + integrate the new capabilities or features at runtime. 29 + 30 + There are two main ways to add classes: 31 + 32 + - **Extensions Directory**: This is a simple way to add new code. It is 33 + less powerful, but takes a lot less work. This is good for quick changes, 34 + testing and development, or getting started on a larger project. 35 + - **Creating Libraries**: This is a more advanced and powerful way to 36 + organize extension code. This is better for larger or longer-lived 37 + projects, or any code which you plan to distribute. 38 + 39 + The next sections walk through these approaches in greater detail. 40 + 41 + 42 + Extensions Directory 43 + ==================== 44 + 45 + The easiest way to extend Phabricator by adding new classes is to drop them 46 + into the extensions directory, at `phabricator/src/extensions/`. 47 + 48 + This is intended as a quick way to add small pieces of functionality, test new 49 + features, or get started on a larger project. Extending Phabricator like this 50 + imposes a small performance penalty compared to using a library. 51 + 52 + This directory exists in all libphutil libraries, so you can find similar 53 + directories in `arcanist/src/extensions/` and `libphutil/src/extensions/`. 54 + 55 + For example, to add a new application, create a file like this one and add it 56 + to `phabricator/src/extensions/`. 57 + 58 + ```name=phabricator/src/extensions/ExampleApplication.php, lang=php 59 + <?php 60 + 61 + final class ExampleApplication extends PhabricatorApplication { 62 + 63 + public function getName() { 64 + return pht('Example'); 65 + } 66 + 67 + } 68 + ``` 69 + 70 + If you load {nav Applications} in the web UI, you should now see your new 71 + application in the list. It won't do anything yet since you haven't defined 72 + any interesting behavior, but this is the basic building block of Phabricator 73 + extensions. 74 + 75 + 76 + Creating Libraries 77 + ================== 78 + 79 + A more powerful (but more complicated) way to extend Phabricator is to create 80 + a libphutil library. Libraries can organize a larger amount of code, are easier 81 + to work with and distribute, and have slightly better performance than loose 82 + source files in the extensions directory. 83 + 84 + In general, you'll perform these one-time setup steps to create a library: 85 + 86 + - Create a new directory. 87 + - Use `arc liberate` to initialize and name the library. 88 + - Configure Phabricator or Arcanist to load the library. 89 + 90 + Then, to add new code, you do this: 91 + 92 + - Write or update classes. 93 + - Update the library metadata by running `arc liberate` again. 94 + 95 + Initializing a Library 96 + ====================== 97 + 98 + To create a new libphutil library, create a directory for it and run 99 + `arc liberate` on the directory. This documentation will use a conventional 100 + directory layout, which is recommended, but you are free to deviate from this. 101 + 102 + ``` 103 + $ mkdir libcustom/ 104 + $ cd libcustom/ 105 + libcustom/ $ arc liberate src/ 106 + ``` 107 + 108 + Now you'll get a prompt like this: 109 + 110 + ```lang=txt 111 + No library currently exists at that path... 112 + The directory '/some/path/libcustom/src' does not exist. 113 + 114 + Do you want to create it? [y/N] y 115 + Creating new libphutil library in '/some/path/libcustom/src'. 116 + Choose a name for the new library. 117 + 118 + What do you want to name this library? 119 + ``` 120 + 121 + Choose a library name (in this case, "libcustom" would be appropriate) and it 122 + you should get some details about the library initialization: 123 + 124 + ```lang=txt 125 + Writing '__phutil_library_init__.php' to 126 + '/some/path/libcustom/src/__phutil_library_init__.php'... 127 + Using library root at 'src'... 128 + Mapping library... 129 + Verifying library... 130 + Finalizing library map... 131 + OKAY Library updated. 132 + ``` 133 + 134 + This will write three files: 135 + 136 + - `src/.phutil_module_cache` This is a cache which makes "arc liberate" 137 + faster when you run it to update the library. You can safely remove it at 138 + any time. If you check your library into version control, you can add this 139 + file to ignore rules (like `.gitignore`). 140 + - `src/__phutil_library_init__.php` This records the name of the library and 141 + tells libphutil that a library exists here. 142 + - `src/__phutil_library_map__.php` This is a map of all the symbols 143 + (functions and classes) in the library, which allows them to be autoloaded 144 + at runtime and dependencies to be statically managed by `arc liberate`. 145 + 146 + Linking with Phabricator 147 + ======================== 148 + 149 + If you aren't using this library with Phabricator (e.g., you are only using it 150 + with Arcanist or are building something else on libphutil) you can skip this 151 + step. 152 + 153 + But, if you intend to use this library with Phabricator, you need to define its 154 + dependency on Phabricator by creating a `.arcconfig` file which points at 155 + Phabricator. For example, you might write this file to 156 + `libcustom/.arcconfig`: 157 + 158 + ```lang=json 159 + { 160 + "load": [ 161 + "phabricator/src/" 162 + ] 163 + } 164 + ``` 165 + 166 + For details on creating a `.arcconfig`, see 167 + @{article:Arcanist User Guide: Configuring a New Project}. In general, this 168 + tells `arc liberate` that it should look for symbols in Phabricator when 169 + performing static analysis. 170 + 171 + NOTE: If Phabricator isn't located next to your custom library, specify a 172 + path which actually points to the `phabricator/` directory. 173 + 174 + You do not need to declare dependencies on `arcanist` or `libphutil`, 175 + since `arc liberate` automatically loads them. 176 + 177 + Finally, edit your Phabricator config to tell it to load your library at 178 + runtime, by adding it to `load-libraries`: 179 + 180 + ```lang=json 181 + ... 182 + 'load-libraries' => array( 183 + 'libcustom' => 'libcustom/src/', 184 + ), 185 + ... 186 + ``` 187 + 188 + Now, Phabricator will be able to load classes from your custom library. 189 + 190 + 191 + Writing Classes 192 + =============== 193 + 194 + To actually write classes, create a new module and put code in it: 195 + 196 + libcustom/ $ mkdir src/example/ 197 + libcustom/ $ nano src/example/ExampleClass.php # Edit some code. 198 + 199 + Now, run `arc liberate` to regenerate the static resource map: 200 + 201 + libcustom/ $ arc liberate src/ 202 + 203 + This will automatically regenerate the static map of the library. 204 + 205 + 206 + What You Can Extend And Invoke 207 + ============================== 208 + 209 + libphutil, Arcanist and Phabricator are strict about extensibility of classes 210 + and visibility of methods and properties. Most classes are marked `final`, and 211 + methods have the minimum required visibility (protected or private). The goal 212 + of this strictness is to make it clear what you can safely extend, access, and 213 + invoke, so your code will keep working as the upstream changes. 214 + 215 + IMPORTANT: We'll still break APIs frequently. The upstream does not support 216 + extension development, and none of these APIs are stable. 217 + 218 + When developing libraries to work with libphutil, Arcanist and Phabricator, you 219 + should respect method and property visibility. 220 + 221 + If you want to add features but can't figure out how to do it without changing 222 + Phabricator code, here are some approaches you may be able to take: 223 + 224 + - {icon check, color=green} **Use Composition**: If possible, use composition 225 + rather than extension to build your feature. 226 + - {icon check, color=green} **Find Another Approach**: Check the 227 + documentation for a better way to accomplish what you're trying to do. 228 + - {icon check, color=green} **File a Feature Request**: Let us know what your 229 + use case is so we can make the class tree more flexible or configurable, or 230 + point you at the right way to do whatever you're trying to do, or explain 231 + why we don't let you do it. Note that we **do not support** extension 232 + development so you may have mixed luck with this one. 233 + 234 + These approaches are **discouraged**, but also possible: 235 + 236 + - {icon times, color=red} **Fork**: Create an ad-hoc local fork and remove 237 + `final` in your copy of the code. This will make it more difficult for you 238 + to upgrade in the future, although it may be the only real way forward 239 + depending on what you're trying to do. 240 + - {icon times, color=red} **Use Reflection**: You can use 241 + [[ http://php.net/manual/en/book.reflection.php | Reflection ]] to remove 242 + modifiers at runtime. This is fragile and discouraged, but technically 243 + possible. 244 + - {icon times, color=red} **Remove Modifiers**: Send us a patch removing 245 + `final` (or turning `protected` or `private` into `public`). We will almost 246 + never accept these patches unless there's a very good reason that the 247 + current behavior is wrong. 248 + 249 + 250 + Next Steps 251 + ========== 252 + 253 + Continue by: 254 + 255 + - visiting the [[ https://secure.phabricator.com/w/community_resources/ | 256 + Community Resources ]] page to find or share extensions and libraries.
-60
src/docs/contributor/darkconsole.diviner
··· 1 - @title Using DarkConsole 2 - @group developer 3 - 4 - Enabling and using the built-in debugging console. 5 - 6 - = Overview = 7 - 8 - DarkConsole is a debugging console built into Phabricator which exposes 9 - configuration, performance and error information. It can help you detect, 10 - understand and resolve bugs and performance problems in Phabricator 11 - applications. 12 - 13 - DarkConsole was originally implemented as part of the Facebook Lite site; its 14 - name is a bit of play on that (and a reference to the dark color palette its 15 - design uses). 16 - 17 - = Warning = 18 - 19 - Because DarkConsole exposes some configuration and debugging information, it is 20 - disabled by default (and **you should not enable it in production**). It has 21 - some simple safeguards to prevent leaking credential information, but enabling 22 - it in production may compromise the integrity of an install. 23 - 24 - = Enabling DarkConsole = 25 - 26 - You enable DarkConsole in your configuration, by setting `darkconsole.enabled` 27 - to `true`, and then turning it on in `Settings` -> `Developer Settings`. Once 28 - DarkConsole is enabled, you can show or hide it by pressing ##`## on your 29 - keyboard. 30 - 31 - Since the setting is not available to logged-out users, you can also set 32 - `darkconsole.always-on` if you need to access DarkConsole on logged-out pages. 33 - 34 - DarkConsole has a number of tabs, each of which is powered by a "plugin". You 35 - can use them to access different debugging and performance features. 36 - 37 - = Plugin: Error Log = 38 - 39 - The "Error Log" plugin shows errors that occurred while generating the page, 40 - similar to the httpd `error.log`. You can send information to the error log 41 - explicitly with the @{function@libphutil:phlog} function. 42 - 43 - If errors occurred, a red dot will appear on the plugin tab. 44 - 45 - = Plugin: Request = 46 - 47 - The "Request" plugin shows information about the HTTP request the server 48 - received, and the server itself. 49 - 50 - = Plugin: Services = 51 - 52 - The "Services" plugin lists calls a page made to external services, like 53 - MySQL and the command line. 54 - 55 - = Plugin: XHProf = 56 - 57 - The "XHProf" plugin gives you access to the XHProf profiler. To use it, you need 58 - to install the corresponding PHP plugin -- see instructions in the 59 - @{article:Installation Guide}. Once it is installed, you can use XHProf to 60 - profile the runtime performance of a page.
-54
src/docs/contributor/installing_xhprof.diviner
··· 1 - @title Installing XHProf 2 - @group developer 3 - 4 - Describes how to install XHProf, a PHP profiling tool. 5 - 6 - Overview 7 - ======== 8 - 9 - You can install XHProf to activate the XHProf tab in DarkConsole and the 10 - `--xprofile` flag from the CLI. This will allow you to generate performance 11 - profiles of pages and scripts, which can be tremendously valuable in identifying 12 - and fixing slow code. 13 - 14 - Installing XHProf 15 - ================= 16 - 17 - XHProf is a PHP profiling tool. You don't need to install it unless you are 18 - developing Phabricator and making performance changes. 19 - 20 - You can install xhprof with: 21 - 22 - $ pecl install xhprof 23 - 24 - If you have a PEAR version prior to 1.9.3, you may run into a `phpize` failure. 25 - If so, you can download the source and build it with: 26 - 27 - $ cd extension/ 28 - $ phpize 29 - $ ./configure 30 - $ make 31 - $ sudo make install 32 - 33 - You may also need to add `extension=xhprof.so` to your php.ini. 34 - 35 - See <https://bugs.php.net/bug.php?id=59747> for more information. 36 - 37 - Using XHProf: Web 38 - ================= 39 - 40 - To profile a web page, activate DarkConsole and navigate to the XHProf tab. 41 - Use the **Profile Page** button to generate a profile. 42 - 43 - Using XHProf: CLI 44 - ================= 45 - 46 - From the command line, use the `--xprofile <filename>` flag to generate a 47 - profile of any script. 48 - 49 - Next Steps 50 - ========== 51 - 52 - Continue by: 53 - 54 - - enabling DarkConsole with @{article:Using DarkConsole}.
+344 -31
src/docs/contributor/internationalization.diviner
··· 9 9 Phabricator partially supports internationalization, but many of the tools 10 10 are missing or in a prototype state. 11 11 12 - This document very briefly summarizes some of what exists today. 12 + This document describes what tools exist today, how to add new translations, 13 + and how to use the translation tools to make a codebase translatable. 14 + 15 + 16 + Adding a New Locale 17 + =================== 18 + 19 + To add a new locale, subclass @{class:PhutilLocale}. This allows you to 20 + introduce a new locale, like "German" or "Klingon". 21 + 22 + Once you've created a locale, applications can add translations for that 23 + locale. 24 + 25 + For instructions on adding new classes, see @{article:Adding New Classes}. 26 + 27 + 28 + Adding Translations to Locale 29 + ============================= 30 + 31 + To translate strings, subclass @{class:PhutilTranslation}. Translations need 32 + to belong to a locale: the locale defines an available language, and each 33 + translation subclass provides strings for it. 34 + 35 + Translations are separated from locales so that third-party applications can 36 + provide translations into different locales without needing to define those 37 + locales themselves. 38 + 39 + For instructions on adding new classes, see @{article:Adding New Classes}. 40 + 13 41 14 42 Writing Translatable Code 15 - ======== 43 + ========================= 16 44 17 45 Strings are marked for translation with @{function@libphutil:pht}. 18 46 19 - Adding a New Locale 20 - ========= 47 + The `pht()` function takes a string (and possibly some parameters) and returns 48 + the translated version of that string in the current viewer's locale, if a 49 + translation is available. 50 + 51 + If text strings will ultimately be read by humans, they should essentially 52 + always be wrapped in `pht()`. For example: 53 + 54 + ```lang=php 55 + $dialog->appendParagraph(pht('This is an example.')); 56 + ``` 57 + 58 + This allows the code to return the correct Spanish or German or Russian 59 + version of the text, if the viewer is using Phabricator in one of those 60 + languages and a translation is available. 61 + 62 + Using `pht()` properly so that strings are translatable can be tricky. Briefly, 63 + the major rules are: 64 + 65 + - Only pass static strings as the first parameter to `pht()`. 66 + - Use parameters to create strings containing user names, object names, etc. 67 + - Translate full sentences, not sentence fragments. 68 + - Let the translation framework handle plural rules. 69 + - Use @{class@libphutil:PhutilNumber} for numbers. 70 + - Let the translation framework handle subject gender rules. 71 + - Translate all human-readable text, even exceptions and error messages. 72 + 73 + See the next few sections for details on these rules. 74 + 75 + 76 + Use Static Strings 77 + ================== 78 + 79 + The first parameter to `pht()` must always be a static string. Broadly, this 80 + means it should not contain variables or function or method calls (it's OK to 81 + split it across multiple lines and concatenate the parts together). 82 + 83 + These are good: 84 + 85 + ```lang=php 86 + pht('The night is dark.'); 87 + pht( 88 + 'Two roads diverged in a yellow wood, '. 89 + 'and sorry I could not travel both '. 90 + 'and be one traveler, long I stood.'); 91 + 92 + ``` 93 + 94 + These won't work (they might appear to work, but are wrong): 95 + 96 + ```lang=php, counterexample 97 + pht(some_function()); 98 + pht('The duck says, '.$quack); 99 + pht($string); 100 + ``` 101 + 102 + The first argument must be a static string so it can be extracted by static 103 + analysis tools and dumped in a big file for translators. If it contains 104 + functions or variables, it can't be extracted, so translators won't be able to 105 + translate it. 106 + 107 + Lint will warn you about problems with use of static strings in calls to 108 + `pht()`. 109 + 110 + 111 + Parameters 112 + ========== 113 + 114 + You can provide parameters to a translation string by using `sprintf()`-style 115 + patterns in the input string. For example: 116 + 117 + ```lang=php 118 + pht('%s earned an award.', $actor); 119 + pht('%s closed %s.', $actor, $task); 120 + ``` 121 + 122 + This is primarily appropriate for usernames, object names, counts, and 123 + untranslatable strings like URIs or instructions to run commands from the CLI. 124 + 125 + Parameters normally should not be used to combine two pieces of translated 126 + text: see the next section for guidance. 127 + 128 + Sentence Fragments 129 + ================== 130 + 131 + You should almost always pass the largest block of text to `pht()` that you 132 + can. Particularly, it's important to pass complete sentences, not try to build 133 + a translation by stringing together sentence fragments. 134 + 135 + There are several reasons for this: 136 + 137 + - It gives translators more context, so they can be more confident they are 138 + producing a satisfying, natural-sounding translation which will make sense 139 + and sound good to native speakers. 140 + - In some languages, one fragment may need to translate differently depending 141 + on what the other fragment says. 142 + - In some languages, the most natural-sounding translation may change the 143 + order of words in the sentence. 144 + 145 + For example, suppose we want to translate these sentence to give the user some 146 + instructions about how to use an interface: 147 + 148 + > Turn the switch to the right. 149 + 150 + > Turn the switch to the left. 151 + 152 + > Turn the dial to the right. 153 + 154 + > Turn the dial to the left. 155 + 156 + Maybe we have a function like this: 157 + 158 + ``` 159 + function get_string($is_switch, $is_right) { 160 + // ... 161 + } 162 + ``` 163 + 164 + One way to write the function body would be like this: 165 + 166 + ```lang=php, counterexample 167 + $what = $is_switch ? pht('switch') : pht('dial'); 168 + $dir = $is_right ? pht('right') : pht('left'); 169 + 170 + return pht('Turn the ').$what.pht(' to the ').$dir.pht('.'); 171 + ``` 172 + 173 + This will work fine in English, but won't work well in other languages. 174 + 175 + One problem with doing this is handling gendered nouns. Languages like Spanish 176 + have gendered nouns, where some nouns are "masculine" and others are 177 + "feminine". The gender of a noun affects which article (in English, the word 178 + "the" is an article) should be used with it. 179 + 180 + In English, we say "**the** knob" and "**the** switch", but a Spanish speaker 181 + would say "**la** perilla" and "**el** interruptor", because the noun for 182 + "knob" in Spanish is feminine (so it is used with the article "la") while the 183 + noun for "switch" is masculine (so it is used with the article "el"). 184 + 185 + A Spanish speaker can not translate the string "Turn the" correctly without 186 + knowing which gender the noun has. Spanish has //two// translations for this 187 + string ("Gira el", "Gira la"), and the form depends on which noun is being 188 + used. 189 + 190 + Another problem is that this reduces flexibility. Translating fragments like 191 + this locks translators into a specific word order, when rearranging the words 192 + might make the sentence sound much more natural to a native speaker. 193 + 194 + For example, if the string read "The knob, to the right, turn it.", it 195 + would technically be English and most English readers would understand the 196 + meaning, but no native English speaker would speak or write like this. 197 + 198 + However, some languages have different subject-verb order rules or 199 + colloquisalisms, and a word order which transliterates like this may sound more 200 + natural to a native speaker. By translating fragments instead of complete 201 + sentences, you lock translators into English word order. 202 + 203 + Finally, the last fragment is just a period. If a translator is presented with 204 + this string in an interface without much context, they have no hope of guessing 205 + how it is used in the software (it could be an end-of-sentence marker, or a 206 + decimal point, or a date separator, or a currency separator, all of which have 207 + very different translations in many locales). It will also conflict with all 208 + other translations of the same string in the codebase, so even if they are 209 + given context they can't translate it without technical problems. 21 210 22 - To add a new locale, subclass @{class:PhutilLocale}. 211 + To avoid these issues, provide complete sentences for translation. This almost 212 + always takes the form of writing out alternatives in full. This is a good way 213 + to implement the example function: 214 + 215 + ```lang=php 216 + if ($is_switch) { 217 + if ($is_right) { 218 + return pht('Turn the switch to the right.'); 219 + } else { 220 + return pht('Turn the switch to the left.'); 221 + } 222 + } else { 223 + if ($is_right) { 224 + return pht('Turn the dial to the right.'); 225 + } else { 226 + return pht('Turn the dial to the left.'); 227 + } 228 + } 229 + ``` 23 230 24 - Translating Strings 25 - ======== 231 + Although this is more verbose, translators can now get genders correct, 232 + rearrange word order, and have far more context when translating. This enables 233 + better, natural-sounding translations which are more satisfying to native 234 + speakers. 26 235 27 - To translate strings, subclass @{class:PhutilTranslation}. 28 236 29 237 Singular and Plural 30 - ======== 238 + =================== 239 + 240 + Different languages have various rules for plural nouns. 241 + 242 + In English there are usually two plural noun forms: for one thing, and any 243 + other number of things. For example, we say that one chair is a "chair" and any 244 + other number of chairs are "chairs": "0 chairs", "1 chair", "2 chairs", etc. 31 245 32 - Different languages have various rules for using singular and plural. All you 33 - need to do is to call @{function@libphutil:pht} with a text that is suitable for 34 - both forms. Example: 246 + In other languages, there are different (and, in some cases, more) plural 247 + forms. For example, in Czech, there are separate forms for "one", "several", 248 + and "many". 249 + 250 + Because plural noun rules depend on the language, you should not write code 251 + which hard-codes English rules. For example, this won't translate well: 252 + 253 + ```lang=php, counterexample 254 + if ($count == 1) { 255 + return pht('This will take an hour.'); 256 + } else { 257 + return pht('This will take hours.'); 258 + } 259 + ``` 260 + 261 + This code is hard-coding the English rule for plural nouns. In languages like 262 + Czech, the correct word for "hours" may be different if the count is 2 or 15, 263 + but a translator won't be able to provide the correct translation if the string 264 + is written like this. 265 + 266 + Instead, pass a generic string to the translation engine which //includes// the 267 + number of objects, and let it handle plural nouns. This is the correct way to 268 + write the translation: 269 + 270 + ```lang=php 271 + return pht('This will take %s hour(s).', new PhutilNumber($count)); 272 + ``` 273 + 274 + If you now load the web UI, you'll see "hour(s)" literally in the UI. To fix 275 + this so the translation sounds better in English, provide translations for this 276 + string in the @{class@phabricator:PhabricatorUSEnglishTranslation} file: 277 + 278 + ```lang=php 279 + 'This will take %s hour(s).' => array( 280 + 'This will take an hour.', 281 + 'This will take hours.', 282 + ), 283 + ``` 284 + 285 + The string will then sound natural in English, but non-English translators will 286 + also be able to produce a natural translation. 287 + 288 + Note that the translations don't actually include the number in this case. The 289 + number is being passed from the code, but that just lets the translation engine 290 + get the rules right: the number does not need to appear in the final 291 + translations shown to the user. 292 + 293 + Using PhutilNumber 294 + ================== 35 295 36 - pht('%d beer(s)', $count); 296 + When translating numbers, you should almost always use `%s` and wrap the count 297 + or number in `new PhutilNumber($count)`. For example: 37 298 38 - Translators will translate this text for all different forms the language uses: 299 + ```lang=php 300 + pht('You have %s experience point(s).', new PhutilNumber($xp)); 301 + ``` 39 302 40 - // English translation 41 - array('%d beer', '%d beers'); 303 + This will let the translation engine handle plural noun rules correctly, and 304 + also format large numbers correctly in a locale-aware way with proper unit and 305 + decimal separators (for example, `1000000` may be printed as "1,000,000", 306 + with commas for readability). 42 307 43 - // Czech translation 44 - array('%d pivo', '%d piva', '%d piv'); 308 + The exception to this rule is IDs which should not be written with unit 309 + separators. For example, this is correct for an object ID: 45 310 46 - The ugly identifier passed to @{function@libphutil:pht} will remain in the text 47 - only if the translation doesn't exist. 311 + ```lang=php 312 + pht('This diff has ID %d.', $diff->getID()); 313 + ``` 48 314 49 315 Male and Female 50 - ======== 316 + =============== 317 + 318 + Different languages also use different words for talking about subjects who are 319 + male, female or have an unknown gender. In English this is mostly just 320 + pronouns (like "he" and "she") but there are more complex rules in other 321 + languages, and languages like Czech also require verb agreement. 322 + 323 + When a parameter refers to a gendered person, pass an object which implements 324 + @{interface@libphutil:PhutilPerson} to `pht()` so translators can provide 325 + gendered translation variants. 326 + 327 + ```lang=php 328 + pht('%s wrote', $actor); 329 + ``` 330 + 331 + Translators will create these translations: 332 + 333 + ```lang=php 334 + // English translation 335 + '%s wrote'; 336 + 337 + // Czech translation 338 + array('%s napsal', '%s napsala'); 339 + ``` 51 340 52 - Different languages use different words for talking about males, females and 53 - unknown genders. Callsites have to call @{function@libphutil:pht} passing 54 - @{class:PhabricatorUser} (or other implementation of 55 - @{interface@libphutil:PhutilPerson}) if talking about the user. Example: 341 + (You usually don't need to worry very much about this rule, it is difficult to 342 + get wrong in standard code.) 56 343 57 - pht('%s wrote', $actor); 58 344 59 - Translators will create this translations: 345 + Exceptions and Errors 346 + ===================== 60 347 61 - // English translation 62 - '%s wrote'; 348 + You should translate all human-readable text, even exceptions and error 349 + messages. This is primarily a rule of convenience which is straightforward 350 + and easy to follow, not a technical rule. 63 351 64 - // Czech translation 65 - array('%s napsal', '%s napsala'); 352 + Some exceptions and error messages don't //technically// need to be translated, 353 + as they will never be shown to a user, but many exceptions and error messages 354 + are (or will become) user-facing on some way. When writing a message, there is 355 + often no clear and objective way to determine which type of message you are 356 + writing. Rather than try to distinguish which are which, we simply translate 357 + all human-readable text. This rule is unambiguous and easy to follow. 358 + 359 + In cases where similar error or exception text is often repeated, it is 360 + probably appropriate to define an exception for that category of error rather 361 + than write the text out repeatedly, anyway. Two examples are 362 + @{class@libphutil:PhutilInvalidStateException} and 363 + @{class@libphutil:PhutilMethodNotImplementedException}, which mostly exist to 364 + produce a consistent message about a common error state in a convenient way. 365 + 366 + There are a handful of error strings in the codebase which may be used before 367 + the translation framework is loaded, or may be used during handling other 368 + errors, possibly rasised from within the translation framework. This handful 369 + of special cases are left untranslated to prevent fatals and cycles in the 370 + error handler. 371 + 372 + 373 + Next Steps 374 + ========== 375 + 376 + Continue by: 377 + 378 + - adding a new locale or translation file with @{article:Adding New Classes}.
+1 -1
src/docs/user/configuration/custom_fields.diviner
··· 207 207 Continue by: 208 208 209 209 - learning more about extending Phabricator with custom code in 210 - @{article:libphutil Libraries User Guide}; 210 + @{article@contributor:Adding New Classes}; 211 211 - or returning to the @{article: Configuration Guide}.
+2 -2
src/docs/user/configuration/managing_daemons.diviner
··· 109 109 just those started with `phd start`. If you're writing a restart script, 110 110 have it launch any custom daemons explicitly after `phd restart`. 111 111 - You can write your own daemons and manage them with `phd` by extending 112 - @{class:PhabricatorDaemon}. See @{article:libphutil Libraries User Guide}. 112 + @{class:PhabricatorDaemon}. See {article@contributor:Adding New Classes}. 113 113 - See @{article:Diffusion User Guide} for details about tuning the repository 114 114 daemon. 115 115 ··· 137 137 138 138 - learning about the repository daemon with @{article:Diffusion User Guide}; 139 139 or 140 - - writing your own daemons with @{article:libphutil Libraries User Guide}. 140 + - writing your own daemons with {article@contributor:Adding New Classes}.
+162
src/docs/user/field/darkconsole.diviner
··· 1 + @title Using DarkConsole 2 + @group fieldmanual 3 + 4 + Enabling and using the built-in debugging and performance console. 5 + 6 + Overview 7 + ======== 8 + 9 + DarkConsole is a debugging console built into Phabricator which exposes 10 + configuration, performance and error information. It can help you detect, 11 + understand and resolve bugs and performance problems in Phabricator 12 + applications. 13 + 14 + 15 + Security Warning 16 + ================ 17 + 18 + WARNING: Because DarkConsole exposes some configuration and debugging 19 + information, it is disabled by default and you should be cautious about 20 + enabling it in production. 21 + 22 + Particularly, DarkConsole may expose some information about your session 23 + details or other private material. It has some crude safeguards against this, 24 + but does not completely sanitize output. 25 + 26 + This is mostly a risk if you take screenshots or copy/paste output and share 27 + it with others. 28 + 29 + 30 + Enabling DarkConsole 31 + ==================== 32 + 33 + You enable DarkConsole in your configuration, by setting `darkconsole.enabled` 34 + to `true`, and then turning it on in {nav Settings > Developer Settings}. 35 + 36 + Once DarkConsole is enabled, you can show or hide it by pressing ##`## on your 37 + keyboard. 38 + 39 + Since the setting is not available to logged-out users, you can also set 40 + `darkconsole.always-on` if you need to access DarkConsole on logged-out pages. 41 + 42 + DarkConsole has a number of tabs, each of which is powered by a "plugin". You 43 + can use them to access different debugging and performance features. 44 + 45 + 46 + Plugin: Error Log 47 + ================= 48 + 49 + The "Error Log" plugin shows errors that occurred while generating the page, 50 + similar to the httpd `error.log`. You can send information to the error log 51 + explicitly with the @{function@libphutil:phlog} function. 52 + 53 + If errors occurred, a red dot will appear on the plugin tab. 54 + 55 + 56 + Plugin: Request 57 + =============== 58 + 59 + The "Request" plugin shows information about the HTTP request the server 60 + received, and the server itself. 61 + 62 + 63 + Plugin: Services 64 + ================ 65 + 66 + The "Services" plugin lists calls a page made to external services, like 67 + MySQL and subprocesses. 68 + 69 + The Services tab can help you understand and debug issues related to page 70 + behavior: for example, you can use it to see exactly what queries or commands a 71 + page is running. In some cases, you can re-run those queries or commands 72 + yourself to examine their output and look for problems. 73 + 74 + This tab can also be particularly useful in understanding page performance, 75 + because many performance problems are caused by inefficient queries (queries 76 + with bad query plans or which take too long) or repeated queries (queries which 77 + could be better structured or benefit from caching). 78 + 79 + When analyzing performance problems, the major things to look for are: 80 + 81 + **Summary**: In the summary table at the top of the tab, are any categories 82 + of events dominating the performance cost? For normal pages, the costs should 83 + be roughly along these lines: 84 + 85 + | Event Type | Approximate Cost | 86 + |---|---| 87 + | Connect | 1%-10% | 88 + | Query | 10%-40% | 89 + | Cache | 1% | 90 + | Event | 1% | 91 + | Conduit | 0%-80% | 92 + | Exec | 0%-80% | 93 + | All Services | 10%-75% | 94 + | Entire Page | 100ms - 1000ms | 95 + 96 + These ranges are rough, but should usually be what you expect from a page 97 + summary. If any of these numbers are way off (for example, "Event" is taking 98 + 50% of runtime), that points toward a possible problem in that section of the 99 + code, and can guide you to examining the related service calls more carefully. 100 + 101 + **Duration**: In the Duration column, look for service calls that take a long 102 + time. Sometimes these calls are just what the page is doing, but sometimes they 103 + may indicate a problem. 104 + 105 + Some questions that may help understanding this column are: are there a small 106 + number of calls which account for a majority of the total page generation time? 107 + Do these calls seem fundamental to the behavior of the page, or is it not clear 108 + why they need to be made? Do some of them seem like they could be cached? 109 + 110 + If there are queries which look slow, using the "Analyze Query Plans" button 111 + may help reveal poor query plans. 112 + 113 + Generally, this column can help pinpoint these kinds of problems: 114 + 115 + - Queries or other service calls which are huge and inefficient. 116 + - Work the page is doing which it could cache instead. 117 + - Problems with network services. 118 + - Missing keys or poor query plans. 119 + 120 + **Repeated Calls**: In the "Details" column, look for service calls that are 121 + being made over and over again. Sometimes this is normal, but usually it 122 + indicates a call that can be batched or cached. 123 + 124 + Some things to look for are: are similar calls being made over and over again? 125 + Do calls mostly make sense given what the page is doing? Could any calls be 126 + cached? Could multiple small calls be collected into one larger call? Are any 127 + of the service calls clearly goofy nonsense that shouldn't be happening? 128 + 129 + Generally, this column can help pinpoint these kinds of problems: 130 + 131 + - Unbatched queries which should be batched (see 132 + @{article:Performance: N+1 Query Problem}). 133 + - Opportunities to improve performance with caching. 134 + - General goofiness in how service calls are woking. 135 + 136 + If the services tab looks fine, and particularly if a page is slow but the 137 + "All Services" cost is small, that may indicate a problem in PHP. The best 138 + tool to understand problems in PHP is XHProf. 139 + 140 + 141 + Plugin: XHProf 142 + ============== 143 + 144 + The "XHProf" plugin gives you access to the XHProf profiler. To use it, you need 145 + to install the corresponding PHP plugin. 146 + 147 + Once it is installed, you can use XHProf to profile the runtime performance of 148 + a page. This will show you a detailed breakdown of where PHP spent time. This 149 + can help find slow or inefficient application code, and is the most powerful 150 + general-purpose performance tool available. 151 + 152 + For instructions on installing and using XHProf, see @{article:Using XHProf}. 153 + 154 + 155 + Next Steps 156 + ========== 157 + 158 + Continue by: 159 + 160 + - installing XHProf with @{article:Using XHProf}; or 161 + - understanding and reporting performance issues with 162 + @{article:Troubleshooting Performance Problems}.
+179
src/docs/user/field/performance.diviner
··· 1 + @title Troubleshooting Performance Problems 2 + @group fieldmanual 3 + 4 + Guide to the troubleshooting slow pages and hangs. 5 + 6 + Overview 7 + ======== 8 + 9 + This document describes how to isolate, examine, understand and resolve or 10 + report performance issues like slow pages and hangs. 11 + 12 + This document covers the general process for handling performance problems, 13 + and outlines the major tools available for understanding them: 14 + 15 + - **Multimeter** helps you understand sources of load and broad resource 16 + utilization. This is a coarse, high-level tool. 17 + - **DarkConsole** helps you dig into a specific slow page and understand 18 + service calls. This is a general, mid-level tool. 19 + - **XHProf** gives you detailed application performance profiles. This 20 + is a fine-grained, low-level tool. 21 + 22 + Performance and the Upstream 23 + ============================ 24 + 25 + Performance issues and hangs will often require upstream involvement to fully 26 + resolve. The intent is for Phabricator to perform well in all reasonable cases, 27 + not require tuning for different workloads (as long as those workloads are 28 + generally reasonable). Poor performance with a reasonable workload is likely a 29 + bug, not a configuration problem. 30 + 31 + However, some pages are slow because Phabricator legitimately needs to do a lot 32 + of work to generate them. For example, if you write a 100MB wiki document, 33 + Phabricator will need substantial time to process it, it will take a long time 34 + to download over the network, and your browser will proably not be able to 35 + render it especially quickly. 36 + 37 + We may be able to improve perfomance in some cases, but Phabricator is not 38 + magic and can not wish away real complexity. The best solution to these problems 39 + is usually to find another way to solve your problem: for example, maybe the 40 + 100MB document can be split into several smaller documents. 41 + 42 + Here are some examples of performance problems under reasonable workloads that 43 + the upstream can help resolve: 44 + 45 + - {icon check, color=green} Commenting on a file and mentioning that same 46 + file results in a hang. 47 + - {icon check, color=green} Creating a new user takes many seconds. 48 + - {icon check, color=green} Loading Feed hangs on 32-bit systems. 49 + 50 + The upstream will be less able to help resolve unusual workloads with high 51 + inherent complexity, like these: 52 + 53 + - {icon times, color=red} A 100MB wiki page takes a long time to render. 54 + - {icon times, color=red} A turing-complete simulation of Conway's Game of 55 + Life implented in 958,000 Herald rules executes slowly. 56 + - {icon times, color=red} Uploading an 8GB file takes several minutes. 57 + 58 + Generally, the path forward will be: 59 + 60 + - Follow the instructions in this document to gain the best understanding of 61 + the issue (and of how to reproduce it) that you can. 62 + - In particular, is it being caused by an unusual workload (like a 100MB 63 + wiki page)? If so, consider other ways to solve the problem. 64 + - File a report with the upstream by following the instructions in 65 + @{article:Contributing Bug Reports}. 66 + 67 + The remaining sections in this document walk through these steps. 68 + 69 + 70 + Understanding Performance Problems 71 + ================================== 72 + 73 + To isolate, examine, and understand performance problems, follow these steps: 74 + 75 + **General Slowness**: If you are experiencing generally poor performance, use 76 + Multimeter to understand resource usage and look for load-based causes. See 77 + @{article:Multimeter User Guide}. If that isn't fruitful, treat this like a 78 + reproducible performance problem on an arbitrary page. 79 + 80 + **Hangs**: If you are experiencing hangs (pages which never return, or which 81 + time out with a fatal after some number of seconds), they are almost always 82 + the result of bugs in the upstream. Report them by following these 83 + instructions: 84 + 85 + - Set `debug.time-limit` to a value like `5`. 86 + - Reproduce the hang. The page should exit after 5 seconds with a more useful 87 + stack trace. 88 + - File a report with the reproduction instructions and the stack trace in 89 + the upstream. See @{article:Contributing Bug Reports} for detailed 90 + instructions. 91 + - Clear `debug.time-limit` again to take your install out of debug mode. 92 + 93 + If part of the reproduction instructions include "Create a 100MB wiki page", 94 + the upstream may be less sympathetic to your cause than if reproducing the 95 + issue does not require an unusual, complex workload. 96 + 97 + In some cases, the hang may really just a very large amount of processing time. 98 + If you're very excited about 100MB wiki pages and don't mind waiting many 99 + minutes for them to render, you may be able to adjust `max_execution_time` in 100 + your PHP configuration to allow the process enough time to complete, or adjust 101 + settings in your webserver config to let it wait longer for results. 102 + 103 + **DarkConsole**: If you have a reproducible performance problem (for example, 104 + loading a specific page is very slow), you can enable DarkConsole (a builtin 105 + debugging console) to examine page performance in detail. 106 + 107 + The two most useful tabs in DarkConsole are the "Services" tab and the 108 + "XHProf" tab. 109 + 110 + The "Services" module allows you to examine service calls (network calls, 111 + subprocesses, events, etc) and find slow queries, slow services, inefficient 112 + query plans, and unnecessary calls. Broadly, you're looking for slow or 113 + repeated service calls, or calls which don't make sense given what the page 114 + should be doing. 115 + 116 + After installing XHProf (see @{article:Using XHProf}) you'll gain access to the 117 + "XHProf" tab, which is a full tracing profiler. You can use the "Profile Page" 118 + button to generate a complete trace of where a page is spending time. When 119 + reading a profile, you're looking for the overall use of time, and for anything 120 + which sticks out as taking unreasonably long or not making sense. 121 + 122 + See @{article:Using DarkConsole} for complete instructions on configuring 123 + and using DarkConsole. 124 + 125 + **AJAX Requests**: To debug Ajax requests, activate DarkConsole and then turn 126 + on the profiler or query analyzer on the main request by clicking the 127 + appropriate button. The setting will cascade to Ajax requests made by the page 128 + and they'll show up in the console with full query analysis or profiling 129 + information. 130 + 131 + **Command-Line Hangs**: If you have a script or daemon hanging, you can send 132 + it `SIGHUP` to have it dump a stack trace to `sys_get_temp_dir()` (usually 133 + `/tmp`). 134 + 135 + Do this with: 136 + 137 + ``` 138 + $ kill -HUP <pid> 139 + ``` 140 + 141 + You can use this command to figure out where the system's temporary directory 142 + is: 143 + 144 + ``` 145 + $ php -r 'echo sys_get_temp_dir()."\n";' 146 + ``` 147 + 148 + On most systems, this is `/tmp`. The trace should appear in that directory with 149 + a name like `phabricator_backtrace_<pid>`. Examining this trace may provide 150 + a key to understanding the problem. 151 + 152 + **Command-Line Performance**: If you have general performance issues with 153 + command-line scripts, you can add `--trace` to see a service call log. This is 154 + similar to the "Services" tab in DarkConsole. This may help identify issues. 155 + 156 + After installing XHProf, you can also add `--xprofile <filename>` to emit a 157 + detailed performance profile. You can `arc upload` these files and then view 158 + them in XHProf from the web UI. 159 + 160 + Next Steps 161 + ========== 162 + 163 + If you've done all you can to isolate and understand the problem you're 164 + experiencing, report it to the upstream. Including as much relevant data as 165 + you can, including: 166 + 167 + - reproduction instructions; 168 + - traces from `debug.time-limit` for hangs; 169 + - screenshots of service call logs from DarkConsole (review these carefully, 170 + as they can sometimes contain sensitive information); 171 + - traces from CLI scripts with `--trace`; 172 + - traces from sending HUP to processes; and 173 + - XHProf profile files from `--xprofile` or "Download .xhprof Profile" in 174 + the web UI. 175 + 176 + After collecting this information: 177 + 178 + - follow the instructions in @{article:Contributing Bug Reports} to file 179 + a report in the upstream.
+122
src/docs/user/field/xhprof.diviner
··· 1 + @title Using XHProf 2 + @group fieldmanual 3 + 4 + Describes how to install and use XHProf, a PHP profiling tool. 5 + 6 + Overview 7 + ======== 8 + 9 + XHProf is a profiling tool which will let you understand application 10 + performance in Phabricator. 11 + 12 + After you install XHProf, you can use it from the web UI and the CLI to 13 + generate detailed performance profiles. It is the most powerful tool available 14 + for understanding application performance and identifying and fixing slow code. 15 + 16 + Installing XHProf 17 + ================= 18 + 19 + You are likely to have the most luck building XHProf from source: 20 + 21 + $ git clone https://github.com/phacility/xhprof.git 22 + 23 + From any source distribution of the extension, build and install it like this: 24 + 25 + $ cd xhprof/ 26 + $ cd extension/ 27 + $ phpize 28 + $ ./configure 29 + $ make 30 + $ sudo make install 31 + 32 + You may also need to add `extension=xhprof.so` to your php.ini. 33 + 34 + You can also try using PECL to install it, but this may not work well with 35 + recent versions of PHP: 36 + 37 + $ pecl install xhprof 38 + 39 + Once you've installed it, `php -i` should report it as installed (you may 40 + see a different version number, which is fine): 41 + 42 + $ php -i | grep xhprof 43 + ... 44 + xhprof => 0.9.2 45 + ... 46 + 47 + 48 + Using XHProf: Web UI 49 + ==================== 50 + 51 + To profile a web page, activate DarkConsole and navigate to the XHProf tab. 52 + Use the **Profile Page** button to generate a profile. 53 + 54 + For instructions on activating DarkConsole, see @{article:Using DarkConsole}. 55 + 56 + 57 + Using XHProf: CLI 58 + ================= 59 + 60 + From the command line, use the `--xprofile <filename>` flag to generate a 61 + profile of any script. 62 + 63 + You can then upload this file to Phabricator (using `arc upload` may be easiest) 64 + and view it in the web UI. 65 + 66 + 67 + Analyzing Profiles 68 + ================== 69 + 70 + Understanding profiles is as much art as science, so be warned that you may not 71 + make much headway. Even if you aren't able to conclusively read a profile 72 + yourself, you can attach profiles when submitting bug reports to the upstream 73 + and we can look at them. This may yield new insight. 74 + 75 + When looking at profiles, the "Wall Time (Inclusive)" column is usually the 76 + most important. This shows the total amount of time spent in a function or 77 + method and all of its children. Usually, to improve the performance of a page, 78 + we're trying to find something that's slow and make it not slow: this column 79 + can help identify which things are slowest. 80 + 81 + The "Wall Time (Exclusive)" column shows time spent in a function or method, 82 + excluding time spent in its children. This can give you hint about whether the 83 + call itself is slow or it's just making calls to other things that are slow. 84 + 85 + You can also get a sense of this by clicking a call to see its children, and 86 + seeing if the bulk of runtime is spent in a child call. This tends to indicate 87 + that you're looking at a problem which is deeper in the stack, and you need 88 + to go down further to identify and understand it. 89 + 90 + Conversely, if the "Wall Time (Exclusive)" column is large, or the children 91 + of a call are all cheap, there's probably something expesive happening in the 92 + call itself. 93 + 94 + The "Count" column can also sometimes tip you off that something is amiss, if 95 + a method which shouldn't be called very often is being called a lot. 96 + 97 + Some general thing to look for -- these aren't smoking guns, but are unusual 98 + and can lead to finding a performance issue: 99 + 100 + - Is a low-level utility method like `phutil_utf8ize()` or `array_merge()` 101 + taking more than a few percent of the page runtime? 102 + - Do any methods (especially high-level methods) have >10,00 calls? 103 + - Are we spending more than 100ms doing anything which isn't loading data 104 + or rendering data? 105 + - Does anything look suspiciously expensive or out of place? 106 + - Is the profile for the slow page a lot different than the profile for a 107 + fast page? 108 + 109 + Some performance problems are obvious and will jump out of a profile; others 110 + may require a more nuanced understanding of the codebase to sniff out which 111 + parts are suspicious. If you aren't able to make progress with a profile, 112 + report the issue upstream and attach the profile to your report. 113 + 114 + 115 + Next Steps 116 + ========== 117 + 118 + Continue by: 119 + 120 + - enabling DarkConsole with @{article:Using DarkConsole}; or 121 + - understanding and reporting performance problems with 122 + @{article:Troubleshooting Performance Problems}.
+1 -1
src/docs/user/userguide/arcanist_lint_unit.diviner
··· 38 38 39 39 If you haven't created a library for the class to live in yet, you need to do 40 40 that first. Follow the instructions in 41 - @{article:libphutil Libraries User Guide}, then make the library loadable by 41 + @{article@contributor:Adding New Classes}, then make the library loadable by 42 42 adding it to your `.arcconfig` like this: 43 43 44 44 {
+1 -1
src/docs/user/userguide/arcanist_new_project.diviner
··· 47 47 48 48 - **load**: list of additional Phutil libraries to load at startup. 49 49 See below for details about path resolution, or see 50 - @{article:libphutil Libraries User Guide} for a general introduction to 50 + @{article@contributor:Adding New Classes} for a general introduction to 51 51 libphutil libraries. 52 52 - **https.cabundle**: specifies the path to an alternate certificate bundle 53 53 for use when making HTTPS connections.
+2 -2
src/docs/user/userguide/events.diviner
··· 21 21 22 22 - Write a listener class which extends @{class@libphutil:PhutilEventListener}. 23 23 - Add it to a libphutil library, or create a new library (for instructions, 24 - see @{article:libphutil Libraries User Guide}. 24 + see @{article@contributor:Adding New Classes}. 25 25 - Configure Phabricator to load the library by adding it to `load-libraries` 26 26 in the Phabricator config. 27 27 - Configure Phabricator to install the event listener by adding the class ··· 38 38 39 39 - Write a listener class which extends @{class@libphutil:PhutilEventListener}. 40 40 - Add it to a libphutil library, or create a new library (for instructions, 41 - see @{article:libphutil Libraries User Guide}. 41 + see @{article@contributor:Adding New Classes}. 42 42 - Configure Phabricator to load the library by adding it to `load` 43 43 in the Arcanist config (e.g., `.arcconfig`, or user/global config). 44 44 - Configure Arcanist to install the event listener by adding the class
-155
src/docs/user/userguide/libraries.diviner
··· 1 - @title libphutil Libraries User Guide 2 - @group userguide 3 - 4 - Guide to creating and managing libphutil libraries. 5 - 6 - = Overview = 7 - 8 - libphutil includes a library system which organizes PHP classes and functions 9 - into modules. Some extensions and customizations of Arcanist and Phabricator 10 - require you to make code available to Phabricator by providing it in a libphutil 11 - library. 12 - 13 - For example, if you want to store files in some kind of custom storage engine, 14 - you need to write a class which can interact with that engine and then tell 15 - Phabricator to load it. 16 - 17 - In general, you perform these one-time setup steps: 18 - 19 - - Create a new directory. 20 - - Use `arc liberate` to initialize and name the library. 21 - - Add a dependency on Phabricator if necessary. 22 - - Add the library to your Phabricator config or `.arcconfig` so it will be 23 - loaded at runtime. 24 - 25 - Then, to add new code, you do this: 26 - 27 - - Write or update classes. 28 - - Update the library metadata by running `arc liberate` again. 29 - 30 - = Creating a New Library = 31 - 32 - To **create a new libphutil library**: 33 - 34 - $ mkdir libcustom/ 35 - $ cd libcustom/ 36 - libcustom/ $ arc liberate src/ 37 - 38 - Now you'll get a prompt like this: 39 - 40 - lang=txt 41 - No library currently exists at that path... 42 - The directory '/some/path/libcustom/src' does not exist. 43 - 44 - Do you want to create it? [y/N] y 45 - Creating new libphutil library in '/some/path/libcustom/src'. 46 - Choose a name for the new library. 47 - 48 - What do you want to name this library? 49 - 50 - Choose a library name (in this case, "libcustom" would be appropriate) and it 51 - you should get some details about the library initialization: 52 - 53 - lang=txt 54 - Writing '__phutil_library_init__.php' to 55 - '/some/path/libcustom/src/__phutil_library_init__.php'... 56 - Using library root at 'src'... 57 - Mapping library... 58 - Verifying library... 59 - Finalizing library map... 60 - OKAY Library updated. 61 - 62 - This will write three files: 63 - 64 - - `src/.phutil_module_cache` This is a cache which makes "arc liberate" 65 - faster when you run it to update the library. You can safely remove it at 66 - any time. If you check your library into version control, you can add this 67 - file to ignore rules (like .gitignore). 68 - - `src/__phutil_library_init__.php` This records the name of the library and 69 - tells libphutil that a library exists here. 70 - - `src/__phutil_library_map__.php` This is a map of all the symbols 71 - (functions and classes) in the library, which allows them to be autoloaded 72 - at runtime and dependencies to be statically managed by "arc liberate". 73 - 74 - = Linking with Phabricator = 75 - 76 - If you aren't using this library with Phabricator (e.g., you are only using it 77 - with Arcanist or are building something else on libphutil) you can skip this 78 - step. 79 - 80 - But, if you intend to use this library with Phabricator, you need to define its 81 - dependency on Phabricator by creating a `.arcconfig` file which points at 82 - Phabricator. For example, you might write this file to 83 - `libcustom/.arcconfig`: 84 - 85 - { 86 - "load": [ 87 - "phabricator/src/" 88 - ] 89 - } 90 - 91 - For details on creating a `.arcconfig`, see 92 - @{article:Arcanist User Guide: Configuring a New Project}. In general, this 93 - tells `arc liberate` that it should look for symbols in Phabricator when 94 - performing static analysis. 95 - 96 - NOTE: If Phabricator isn't located next to your custom library, specify a 97 - path which actually points to the `phabricator/` directory. 98 - 99 - You do not need to declare dependencies on `arcanist` or `libphutil`, 100 - since `arc liberate` automatically loads them. 101 - 102 - Finally, edit your Phabricator config to tell it to load your library at 103 - runtime, by adding it to `load-libraries`: 104 - 105 - ... 106 - 'load-libraries' => array( 107 - 'libcustom' => 'libcustom/src/', 108 - ), 109 - ... 110 - 111 - Now, Phabricator will be able to load classes from your custom library. 112 - 113 - = Writing Classes = 114 - 115 - To actually write classes, create a new module and put code in it: 116 - 117 - libcustom/ $ mkdir src/example/ 118 - libcustom/ $ nano src/example/ExampleClass.php # Edit some code. 119 - 120 - Now, run `arc liberate` to regenerate the static resource map: 121 - 122 - libcustom/ $ arc liberate src/ 123 - 124 - This will automatically regenerate the static map of the library. 125 - 126 - = What You Can Extend And Invoke = 127 - 128 - libphutil, Arcanist and Phabricator are strict about extensibility of classes 129 - and visibility of methods and properties. Most classes are marked `final`, and 130 - methods have the minimum required visibility (protected or private). The goal of 131 - this strictness is to make it clear what you can safely extend, access, and 132 - invoke, so your code will keep working as the upstream changes. 133 - 134 - When developing libraries to work with libphutil, Arcanist and Phabricator, you 135 - should respect method and property visibility and extend only classes marked 136 - `@stable`. They are rendered with a large callout in the documentation (for 137 - example: @{class@libphutil:AbstractDirectedGraph}). These classes are external 138 - interfaces intended for extension. 139 - 140 - If you want to extend a class but it is not marked `@stable`, here are some 141 - approaches you can take: 142 - 143 - - Good: If possible, use composition rather than extension to build your 144 - feature. 145 - - Good: Check the documentation for a better way to accomplish what you're 146 - trying to do. 147 - - Good: Let us know what your use case is so we can make the class tree more 148 - flexible or configurable, or point you at the right way to do whatever 149 - you're trying to do, or explain why we don't let you do it. 150 - - Discouraged: Send us a patch removing "final" (or turning "protected" or 151 - "private" into "public"). We generally will not accept these patches, unless 152 - there's a good reason that the current behavior is wrong. 153 - - Discouraged: Create an ad-hoc local fork and remove "final" in your copy of 154 - the code. This will make it more difficult for you to upgrade in the future. 155 - - Discouraged: Use Reflection to violate visibility keywords.
+99
src/docs/user/userguide/multimeter.diviner
··· 1 + @title Multimeter User Guide 2 + @group userguide 3 + 4 + Using Multimeter, a sampling profiler. 5 + 6 + Overview 7 + ======== 8 + 9 + IMPORTANT: This document describes a prototype application. 10 + 11 + Multimeter is a sampling profiler that can give you coarse information about 12 + Phabricator resource usage. In particular, it can help quickly identify sources 13 + of load, like bots or scripts which are making a very large number of requests. 14 + 15 + Configuring and Using Multimeter 16 + ================================ 17 + 18 + To access Multimeter, go to {nav Applications > Multimeter}. 19 + 20 + By default, Multimeter samples 0.1% of pages. This should be a reasonable rate 21 + for most installs, but you can increase or decrease the rate by adjusting 22 + `debug.sample-rate`. Increasing the rate (by setting the value to a lower 23 + number, like 100, to sample 1% of pages) will increase the granualrity of the 24 + data, at a small performance cost. 25 + 26 + Using Multimeter 27 + ================ 28 + 29 + Multimeter shows you what Phabricator has spent time doing recently. By 30 + looking at the samples it collects, you can identify major sources of load 31 + or resource use, whether they are specific users, pages, subprocesses, or 32 + other types of activity. 33 + 34 + By identifying and understanding unexpected load, you can adjust usage patterns 35 + or configuration to make better use of resources (for example, rewrite bots 36 + that are making too many calls), or report specific, actionable issues to the 37 + upstream for resolution. 38 + 39 + The main screen of Multimeter shows you everything Phabricator has spent 40 + resources on recently, broken down by action type. Categories are folded up 41 + by default, with "(All)" labels. 42 + 43 + To filter by a dimension, click the link for it. For example, from the main 44 + page, you can click "Web Request" to filter by only web requests. To expand a 45 + grouped dimension, click the "(All)" link. 46 + 47 + For example, suppose we suspect that someone is running a bot that is making 48 + a lot of requests and consuming a lot of resources. We can get a better idea 49 + about this by filtering the results like this: 50 + 51 + - Click {nav Web Request}. This will show only web requests. 52 + - Click {nav (All)} under "Viewer". This will expand events by viewer. 53 + 54 + Recent resource costs for web requests are now shown, grouped and sorted by 55 + user. The usernames in the "Viewer" column show who is using resources, in 56 + order from greatest use to least use (only administrators can see usernames). 57 + 58 + The "Avg" column shows the average cost per event, while the "Cost" column 59 + shows the total cost. 60 + 61 + If the top few users account for similar costs and are normal, active users, 62 + there may be nothing amiss and your problem might lie elsewhere. If a user like 63 + `slowbot` is in the top few users and has way higher usage than anyone else, 64 + there might be a script running under that account consuming a disproportionate 65 + amount of resources. 66 + 67 + Assuming you find a user with unusual usage, you could dig into their usage 68 + like this: 69 + 70 + - Click their name (like {nav slowbot}) to filter to just their requests. 71 + - Click {nav (All)} under "Label". This expands by request detail. 72 + 73 + This will show exactly what they spent those resources doing, and can help 74 + identify if they're making a lot of API calls or scraping the site or whatever 75 + else. 76 + 77 + This is just an example of a specific kind of problem that Multimeter could 78 + help resolve. In general, exploring Multimeter data by filtering and expanding 79 + resource uses can help you understand how resources are used and identify 80 + unexpected uses of resources. For example: 81 + 82 + - Identify a problem with load balancing by filtering on {nav Web Request} 83 + and expanding on {nav Host}. If hosts aren't roughly even, DNS or a load 84 + balancer are misconfigured. 85 + - Identify which pages cost the most by filtering on {nav Web Request} 86 + and expanding on {nav Label}. 87 + - Find outlier pages by filtering on {nav Web Request} and expanding on 88 + {nav ID}. 89 + - Find where subprocess are invoked from by filtering on {nav Subprocesses}, 90 + then expanding on {nav Context}. 91 + 92 + 93 + Next Steps 94 + ========== 95 + 96 + Continue by: 97 + 98 + - understanding and reporting performance issues with 99 + @{article:Troubleshooting Performance Problems}.