private function buildPropertyListView(PhabricatorDaemonLog $daemon) { $request = $this->getRequest(); $viewer = $request->getUser(); $view = id(new PHUIPropertyListView())->setUser($viewer); $id = $daemon->getID(); $c_epoch = $daemon->getDateCreated(); $u_epoch = $daemon->getDateModified(); $unknown_time = PhabricatorDaemonLogQuery::getTimeUntilUnknown(); $dead_time = PhabricatorDaemonLogQuery::getTimeUntilDead(); $wait_time = PhutilDaemonHandle::getWaitBeforeRestart(); $details = null; $status = $daemon->getStatus(); switch ($status) { case PhabricatorDaemonLog::STATUS_RUNNING: $details = pht('This daemon is running normally and reported a status update ' . 'recently (within %s).', phutil_format_relative_time($unknown_time)); break; case PhabricatorDaemonLog::STATUS_UNKNOWN: $details = pht('This daemon has not reported a status update recently (within %s). ' . 'It may have exited abruptly. After %s, it will be presumed dead.', phutil_format_relative_time($unknown_time), phutil_format_relative_time($dead_time)); break; case PhabricatorDaemonLog::STATUS_DEAD: $details = pht('This daemon did not report a status update for %s. It is ' . 'presumed dead. Usually, this indicates that the daemon was ' . 'killed or otherwise exited abruptly with an error. You may ' . 'need to restart it.', phutil_format_relative_time($dead_time)); break; case PhabricatorDaemonLog::STATUS_WAIT: $details = pht('This daemon is running normally and reported a status update ' . 'recently (within %s). However, it encountered an error while ' . 'doing work and is waiting a little while (%s) to resume ' . 'processing. After encountering an error, daemons wait before ' . 'resuming work to avoid overloading services.', phutil_format_relative_time($unknown_time), phutil_format_relative_time($wait_time)); break; case PhabricatorDaemonLog::STATUS_EXITING: $details = pht('This daemon is shutting down gracefully.'); break; case PhabricatorDaemonLog::STATUS_EXITED: $details = pht('This daemon exited normally and is no longer running.'); break; } $view->addProperty(pht('Status Details'), $details); $view->addProperty(pht('Daemon Class'), $daemon->getDaemon()); $view->addProperty(pht('Host'), $daemon->getHost()); $view->addProperty(pht('PID'), $daemon->getPID()); $view->addProperty(pht('Running as'), $daemon->getRunningAsUser()); $view->addProperty(pht('Started'), phabricator_datetime($c_epoch, $viewer)); $view->addProperty(pht('Seen'), pht('%s ago (%s)', phutil_format_relative_time(time() - $u_epoch), phabricator_datetime($u_epoch, $viewer))); $argv = $daemon->getArgv(); if (is_array($argv)) { $argv = implode("\n", $argv); } $view->addProperty(pht('Argv'), phutil_tag('textarea', array('style' => 'width: 100%; height: 12em;'), $argv)); $view->addProperty(pht('View Full Logs'), phutil_tag('tt', array(), "phabricator/ \$ ./bin/phd log --id {$id}")); return $view; }
public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); $window_start = time() - 60 * 15; // Assume daemons spend about 250ms second in overhead per task acquiring // leases and doing other bookkeeping. This is probably an over-estimation, // but we'd rather show that utilization is too high than too low. $lease_overhead = 0.25; $completed = id(new PhabricatorWorkerArchiveTaskQuery())->withDateModifiedSince($window_start)->execute(); $failed = id(new PhabricatorWorkerActiveTask())->loadAllWhere('failureTime > %d', $window_start); $usage_total = 0; $usage_start = PHP_INT_MAX; $completed_info = array(); foreach ($completed as $completed_task) { $class = $completed_task->getTaskClass(); if (empty($completed_info[$class])) { $completed_info[$class] = array('n' => 0, 'duration' => 0); } $completed_info[$class]['n']++; $duration = $completed_task->getDuration(); $completed_info[$class]['duration'] += $duration; // NOTE: Duration is in microseconds, but we're just using seconds to // compute utilization. $usage_total += $lease_overhead + $duration / 1000000; $usage_start = min($usage_start, $completed_task->getDateModified()); } $completed_info = isort($completed_info, 'n'); $rows = array(); foreach ($completed_info as $class => $info) { $rows[] = array($class, number_format($info['n']), pht('%s us', new PhutilNumber((int) ($info['duration'] / $info['n'])))); } if ($failed) { // Add the time it takes to restart the daemons. This includes a guess // about other overhead of 2X. $restart_delay = PhutilDaemonHandle::getWaitBeforeRestart(); $usage_total += $restart_delay * count($failed) * 2; foreach ($failed as $failed_task) { $usage_start = min($usage_start, $failed_task->getFailureTime()); } $rows[] = array(phutil_tag('em', array(), pht('Temporary Failures')), count($failed), null); } $logs = id(new PhabricatorDaemonLogQuery())->setViewer($viewer)->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE)->setAllowStatusWrites(true)->execute(); $taskmasters = 0; foreach ($logs as $log) { if ($log->getDaemon() == 'PhabricatorTaskmasterDaemon') { $taskmasters++; } } if ($taskmasters && $usage_total) { // Total number of wall-time seconds the daemons have been running since // the oldest event. For very short times round up to 15s so we don't // render any ridiculous numbers if you reload the page immediately after // restarting the daemons. $available_time = $taskmasters * max(15, time() - $usage_start); // Percentage of those wall-time seconds we can account for, which the // daemons spent doing work: $used_time = $usage_total / $available_time; $rows[] = array(phutil_tag('em', array(), pht('Queue Utilization (Approximate)')), sprintf('%.1f%%', 100 * $used_time), null); } $completed_table = new AphrontTableView($rows); $completed_table->setNoDataString(pht('No tasks have completed in the last 15 minutes.')); $completed_table->setHeaders(array(pht('Class'), pht('Count'), pht('Avg'))); $completed_table->setColumnClasses(array('wide', 'n', 'n')); $completed_panel = id(new PHUIObjectBoxView())->setHeaderText(pht('Recently Completed Tasks (Last 15m)'))->setTable($completed_table); $daemon_table = id(new PhabricatorDaemonLogListView())->setUser($viewer)->setDaemonLogs($logs); $daemon_panel = id(new PHUIObjectBoxView())->setHeaderText(pht('Active Daemons'))->setTable($daemon_table); $tasks = id(new PhabricatorWorkerLeaseQuery())->setSkipLease(true)->withLeasedTasks(true)->setLimit(100)->execute(); $tasks_table = id(new PhabricatorDaemonTasksTableView())->setTasks($tasks)->setNoDataString(pht('No tasks are leased by workers.')); $leased_panel = id(new PHUIObjectBoxView())->setHeaderText(pht('Leased Tasks'))->setTable($tasks_table); $task_table = new PhabricatorWorkerActiveTask(); $queued = queryfx_all($task_table->establishConnection('r'), 'SELECT taskClass, count(*) N FROM %T GROUP BY taskClass ORDER BY N DESC', $task_table->getTableName()); $rows = array(); foreach ($queued as $row) { $rows[] = array($row['taskClass'], number_format($row['N'])); } $queued_table = new AphrontTableView($rows); $queued_table->setHeaders(array(pht('Class'), pht('Count'))); $queued_table->setColumnClasses(array('wide', 'n')); $queued_table->setNoDataString(pht('Task queue is empty.')); $queued_panel = new PHUIObjectBoxView(); $queued_panel->setHeaderText(pht('Queued Tasks')); $queued_panel->setTable($queued_table); $upcoming = id(new PhabricatorWorkerLeaseQuery())->setLimit(10)->setSkipLease(true)->execute(); $upcoming_panel = id(new PHUIObjectBoxView())->setHeaderText(pht('Next In Queue'))->setTable(id(new PhabricatorDaemonTasksTableView())->setTasks($upcoming)->setNoDataString(pht('Task queue is empty.'))); $triggers = id(new PhabricatorWorkerTriggerQuery())->setViewer($viewer)->setOrder(PhabricatorWorkerTriggerQuery::ORDER_EXECUTION)->withNextEventBetween(0, null)->needEvents(true)->setLimit(10)->execute(); $triggers_table = $this->buildTriggersTable($triggers); $triggers_panel = id(new PHUIObjectBoxView())->setHeaderText(pht('Upcoming Triggers'))->setTable($triggers_table); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Console')); $nav = $this->buildSideNavView(); $nav->selectFilter('/'); $nav->appendChild(array($crumbs, $completed_panel, $daemon_panel, $queued_panel, $leased_panel, $upcoming_panel, $triggers_panel)); return $this->newPage()->setTitle(pht('Console'))->appendChild($nav); }