@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
at upstream/main 291 lines 8.7 kB view raw
1<?php 2 3final class PhabricatorDaemonConsoleController 4 extends PhabricatorDaemonController { 5 6 public function handleRequest(AphrontRequest $request) { 7 $viewer = $this->getViewer(); 8 9 $window_start = (time() - (60 * 15)); 10 11 // Assume daemons spend about 250ms second in overhead per task acquiring 12 // leases and doing other bookkeeping. This is probably an over-estimation, 13 // but we'd rather show that utilization is too high than too low. 14 $lease_overhead = 0.250; 15 16 $completed = id(new PhabricatorWorkerArchiveTaskQuery()) 17 ->withDateModifiedSince($window_start) 18 ->execute(); 19 20 $failed = id(new PhabricatorWorkerActiveTask())->loadAllWhere( 21 'failureTime > %d', 22 $window_start); 23 24 $usage_total = 0; 25 $usage_start = PHP_INT_MAX; 26 27 $completed_info = array(); 28 foreach ($completed as $completed_task) { 29 $class = $completed_task->getTaskClass(); 30 if (empty($completed_info[$class])) { 31 $completed_info[$class] = array( 32 'n' => 0, 33 'duration' => 0, 34 'queueTime' => 0, 35 ); 36 } 37 $completed_info[$class]['n']++; 38 $duration = $completed_task->getDuration(); 39 $completed_info[$class]['duration'] += $duration; 40 41 // NOTE: Duration is in microseconds, but we're just using seconds to 42 // compute utilization. 43 $usage_total += $lease_overhead + ($duration / 1000000); 44 $usage_start = min($usage_start, $completed_task->getDateModified()); 45 46 $date_archived = $completed_task->getArchivedEpoch(); 47 $queue_seconds = $date_archived - $completed_task->getDateCreated(); 48 49 // Don't measure queue time for tasks that completed in the same 50 // epoch-second they were created in. 51 if ($queue_seconds > 0) { 52 $sec_in_us = phutil_units('1 second in microseconds'); 53 $queue_us = $queue_seconds * $sec_in_us; 54 $queue_exclusive_us = $queue_us - $duration; 55 $queue_exclusive_seconds = $queue_exclusive_us / $sec_in_us; 56 $rounded = floor($queue_exclusive_seconds); 57 $completed_info[$class]['queueTime'] += $rounded; 58 } 59 } 60 61 $completed_info = isort($completed_info, 'n'); 62 63 $rows = array(); 64 foreach ($completed_info as $class => $info) { 65 $duration_avg = new PhutilNumber((int)($info['duration'] / $info['n'])); 66 $queue_avg = new PhutilNumber((int)($info['queueTime'] / $info['n'])); 67 $rows[] = array( 68 $class, 69 number_format($info['n']), 70 pht('%s us', $duration_avg), 71 pht('%s s', $queue_avg), 72 ); 73 } 74 75 if ($failed) { 76 // Add the time it takes to restart the daemons. This includes a guess 77 // about other overhead of 2X. 78 $restart_delay = PhutilDaemonHandle::getWaitBeforeRestart(); 79 $usage_total += $restart_delay * count($failed) * 2; 80 foreach ($failed as $failed_task) { 81 $usage_start = min($usage_start, $failed_task->getFailureTime()); 82 } 83 84 $rows[] = array( 85 phutil_tag('em', array(), pht('Temporary Failures')), 86 count($failed), 87 null, 88 null, 89 ); 90 } 91 92 $logs = id(new PhabricatorDaemonLogQuery()) 93 ->setViewer($viewer) 94 ->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE) 95 ->setAllowStatusWrites(true) 96 ->execute(); 97 98 $taskmasters = 0; 99 foreach ($logs as $log) { 100 if ($log->getDaemon() == 'PhabricatorTaskmasterDaemon') { 101 $taskmasters++; 102 } 103 } 104 105 if ($taskmasters && $usage_total) { 106 // Total number of wall-time seconds the daemons have been running since 107 // the oldest event. For very short times round up to 15s so we don't 108 // render any ridiculous numbers if you reload the page immediately after 109 // restarting the daemons. 110 $available_time = $taskmasters * max(15, (time() - $usage_start)); 111 112 // Percentage of those wall-time seconds we can account for, which the 113 // daemons spent doing work: 114 $used_time = ($usage_total / $available_time); 115 116 $rows[] = array( 117 phutil_tag('em', array(), pht('Queue Utilization (Approximate)')), 118 sprintf('%.1f%%', 100 * $used_time), 119 null, 120 null, 121 ); 122 } 123 124 $completed_table = new AphrontTableView($rows); 125 $completed_table->setNoDataString( 126 pht('No tasks have completed in the last 15 minutes.')); 127 $completed_table->setHeaders( 128 array( 129 pht('Class'), 130 pht('Count'), 131 pht('Average Duration'), 132 pht('Average Queue Time'), 133 )); 134 $completed_table->setColumnClasses( 135 array( 136 'wide', 137 'n', 138 'n', 139 'n', 140 )); 141 142 $completed_panel = id(new PHUIObjectBoxView()) 143 ->setHeaderText(pht('Recently Completed Tasks (Last 15m)')) 144 ->setTable($completed_table); 145 146 $daemon_table = id(new PhabricatorDaemonLogListView()) 147 ->setUser($viewer) 148 ->setDaemonLogs($logs); 149 150 $daemon_panel = id(new PHUIObjectBoxView()) 151 ->setHeaderText(pht('Active Daemons')) 152 ->setTable($daemon_table); 153 154 $tasks = id(new PhabricatorWorkerLeaseQuery()) 155 ->setSkipLease(true) 156 ->withLeasedTasks(true) 157 ->setLimit(100) 158 ->execute(); 159 160 $tasks_table = id(new PhabricatorDaemonTasksTableView()) 161 ->setTasks($tasks) 162 ->setNoDataString(pht('No tasks are leased by workers.')); 163 164 $leased_panel = id(new PHUIObjectBoxView()) 165 ->setHeaderText(pht('Leased Tasks')) 166 ->setTable($tasks_table); 167 168 $task_table = new PhabricatorWorkerActiveTask(); 169 $queued = queryfx_all( 170 $task_table->establishConnection('r'), 171 'SELECT taskClass, count(*) N FROM %T GROUP BY taskClass 172 ORDER BY N DESC', 173 $task_table->getTableName()); 174 175 $rows = array(); 176 foreach ($queued as $row) { 177 $rows[] = array( 178 $row['taskClass'], 179 number_format($row['N']), 180 ); 181 } 182 183 $queued_table = new AphrontTableView($rows); 184 $queued_table->setHeaders( 185 array( 186 pht('Class'), 187 pht('Count'), 188 )); 189 $queued_table->setColumnClasses( 190 array( 191 'wide', 192 'n', 193 )); 194 $queued_table->setNoDataString(pht('Task queue is empty.')); 195 196 $queued_panel = new PHUIObjectBoxView(); 197 $queued_panel->setHeaderText(pht('Queued Tasks')); 198 $queued_panel->setTable($queued_table); 199 200 $upcoming = id(new PhabricatorWorkerLeaseQuery()) 201 ->setLimit(10) 202 ->setSkipLease(true) 203 ->execute(); 204 205 $upcoming_panel = id(new PHUIObjectBoxView()) 206 ->setHeaderText(pht('Next In Queue')) 207 ->setTable( 208 id(new PhabricatorDaemonTasksTableView()) 209 ->setTasks($upcoming) 210 ->setNoDataString(pht('Task queue is empty.'))); 211 212 $triggers = id(new PhabricatorWorkerTriggerQuery()) 213 ->setViewer($viewer) 214 ->setOrder(PhabricatorWorkerTriggerQuery::ORDER_EXECUTION) 215 ->withNextEventBetween(0, null) 216 ->needEvents(true) 217 ->setLimit(10) 218 ->execute(); 219 220 $triggers_table = $this->buildTriggersTable($triggers); 221 222 $triggers_panel = id(new PHUIObjectBoxView()) 223 ->setHeaderText(pht('Upcoming Triggers')) 224 ->setTable($triggers_table); 225 226 $crumbs = $this->buildApplicationCrumbs(); 227 $crumbs->addTextCrumb(pht('Console')); 228 229 $nav = $this->buildSideNavView(); 230 $nav->selectFilter('/'); 231 $nav->appendChild( 232 array( 233 $crumbs, 234 $completed_panel, 235 $daemon_panel, 236 $queued_panel, 237 $leased_panel, 238 $upcoming_panel, 239 $triggers_panel, 240 )); 241 242 return $this->newPage() 243 ->setTitle(pht('Console')) 244 ->appendChild($nav); 245 246 } 247 248 private function buildTriggersTable(array $triggers) { 249 $viewer = $this->getViewer(); 250 251 $rows = array(); 252 foreach ($triggers as $trigger) { 253 $event = $trigger->getEvent(); 254 if ($event) { 255 $last_epoch = $event->getLastEventEpoch(); 256 $next_epoch = $event->getNextEventEpoch(); 257 } else { 258 $last_epoch = null; 259 $next_epoch = null; 260 } 261 262 $rows[] = array( 263 $trigger->getID(), 264 $trigger->getClockClass(), 265 $trigger->getActionClass(), 266 $last_epoch ? phabricator_datetime($last_epoch, $viewer) : null, 267 $next_epoch ? phabricator_datetime($next_epoch, $viewer) : null, 268 ); 269 } 270 271 return id(new AphrontTableView($rows)) 272 ->setNoDataString(pht('There are no upcoming event triggers.')) 273 ->setHeaders( 274 array( 275 pht('ID'), 276 pht('Clock'), 277 pht('Action'), 278 pht('Last'), 279 pht('Next'), 280 )) 281 ->setColumnClasses( 282 array( 283 '', 284 '', 285 'wide', 286 'date', 287 'date', 288 )); 289 } 290 291}