2011-03-10 22:48:29 +01:00
|
|
|
<?php
|
|
|
|
|
2012-03-10 00:46:25 +01:00
|
|
|
final class PhabricatorDaemonConsoleController
|
|
|
|
extends PhabricatorDaemonController {
|
2011-03-10 22:48:29 +01:00
|
|
|
|
2015-01-20 22:32:43 +01:00
|
|
|
public function handleRequest(AphrontRequest $request) {
|
|
|
|
$viewer = $this->getViewer();
|
Use phabricator_ time functions in more places
Summary:
Replace some more date() calls with locale-aware calls.
Also, at least on my system, the DateTimeZone / DateTime stuff didn't actually
work and always rendered in UTC. Fixed that.
Test Plan:
Viewed daemon console, differential revisions, files, and maniphest timestamps
in multiple timezones.
Reviewed By: toulouse
Reviewers: toulouse, fratrik, jungejason, aran, tuomaspelkonen
CC: aran, toulouse
Differential Revision: 530
2011-06-26 18:22:52 +02:00
|
|
|
|
Show a queue utilization statistic in the Daemon console
Summary:
This came up recently in a discussion with @lifeihuang, and then tangentally with @hach-que. Make it easier for users to get a sense of whether they might need to add more daemons. Although we've improved the transparency of daemons, it's not easy for non-experts to determine at a glance how close to overflowing the queue is.
This number is approximate, but should be good enough for determining if your queue is more like 25% or 95% full.
If this goes over, say, 80%, it's probably a good idea to think about adding a couple of daemons. If it's under that, you should generally be fine.
Test Plan: {F88331}
Reviewers: btrahan, hach-que, lifeihuang
Reviewed By: btrahan
CC: hach-que, lifeihuang, aran, chad
Differential Revision: https://secure.phabricator.com/D7747
2013-12-09 22:22:22 +01:00
|
|
|
$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.250;
|
|
|
|
|
2014-12-24 00:45:42 +01:00
|
|
|
$completed = id(new PhabricatorWorkerArchiveTaskQuery())
|
|
|
|
->withDateModifiedSince($window_start)
|
|
|
|
->execute();
|
Improve Daemon console UI
Summary:
Makes various fixes to the Daemon console UI:
- Removes timeline, timeline cursors, and timeline-related controllers. This abstraction is all but dead and just waiting on an eventual cleanup effort with Facebook (see T2003). There's no need to inspect or debug it anymore.
- Instead of showing the 15 most recently launched non-exited daemons, show all the running daemons. With the old rule, "dead" daemons tended to build up at the bottom of the list -- e.g., secure.phabricator.com shows the 7 active daemons, then 8 dead daemons from as far back as Aug 2012. Showing running daemons is far more useful.
- Simplify the two "Running Daemons" and "All Daemons" subviews into one "All Daemons" subview. The main console now has "running daemons", effectively.
- Create a "Recently completed tasks" view, which shows how many tasks of each task class have completed in the last 15 minutes and how long they took on average. Understanding how quickly tasks are completing is one of the most common uses of the daemon console, and it's currently almost useless for that. Now that we archive tasks, we can show this information in an easily digestable form.
- Partially modernize all of the remaining views.
Test Plan: Looked at daemon console.
Reviewers: btrahan, chad, vrana
Reviewed By: btrahan
CC: aran
Maniphest Tasks: T2372, T2003
Differential Revision: https://secure.phabricator.com/D4573
2013-01-22 21:14:33 +01:00
|
|
|
|
2013-07-23 23:30:16 +02:00
|
|
|
$failed = id(new PhabricatorWorkerActiveTask())->loadAllWhere(
|
|
|
|
'failureTime > %d',
|
Show a queue utilization statistic in the Daemon console
Summary:
This came up recently in a discussion with @lifeihuang, and then tangentally with @hach-que. Make it easier for users to get a sense of whether they might need to add more daemons. Although we've improved the transparency of daemons, it's not easy for non-experts to determine at a glance how close to overflowing the queue is.
This number is approximate, but should be good enough for determining if your queue is more like 25% or 95% full.
If this goes over, say, 80%, it's probably a good idea to think about adding a couple of daemons. If it's under that, you should generally be fine.
Test Plan: {F88331}
Reviewers: btrahan, hach-que, lifeihuang
Reviewed By: btrahan
CC: hach-que, lifeihuang, aran, chad
Differential Revision: https://secure.phabricator.com/D7747
2013-12-09 22:22:22 +01:00
|
|
|
$window_start);
|
|
|
|
|
|
|
|
$usage_total = 0;
|
|
|
|
$usage_start = PHP_INT_MAX;
|
2013-07-23 23:30:16 +02:00
|
|
|
|
Improve Daemon console UI
Summary:
Makes various fixes to the Daemon console UI:
- Removes timeline, timeline cursors, and timeline-related controllers. This abstraction is all but dead and just waiting on an eventual cleanup effort with Facebook (see T2003). There's no need to inspect or debug it anymore.
- Instead of showing the 15 most recently launched non-exited daemons, show all the running daemons. With the old rule, "dead" daemons tended to build up at the bottom of the list -- e.g., secure.phabricator.com shows the 7 active daemons, then 8 dead daemons from as far back as Aug 2012. Showing running daemons is far more useful.
- Simplify the two "Running Daemons" and "All Daemons" subviews into one "All Daemons" subview. The main console now has "running daemons", effectively.
- Create a "Recently completed tasks" view, which shows how many tasks of each task class have completed in the last 15 minutes and how long they took on average. Understanding how quickly tasks are completing is one of the most common uses of the daemon console, and it's currently almost useless for that. Now that we archive tasks, we can show this information in an easily digestable form.
- Partially modernize all of the remaining views.
Test Plan: Looked at daemon console.
Reviewers: btrahan, chad, vrana
Reviewed By: btrahan
CC: aran
Maniphest Tasks: T2372, T2003
Differential Revision: https://secure.phabricator.com/D4573
2013-01-22 21:14:33 +01:00
|
|
|
$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;
|
Show a queue utilization statistic in the Daemon console
Summary:
This came up recently in a discussion with @lifeihuang, and then tangentally with @hach-que. Make it easier for users to get a sense of whether they might need to add more daemons. Although we've improved the transparency of daemons, it's not easy for non-experts to determine at a glance how close to overflowing the queue is.
This number is approximate, but should be good enough for determining if your queue is more like 25% or 95% full.
If this goes over, say, 80%, it's probably a good idea to think about adding a couple of daemons. If it's under that, you should generally be fine.
Test Plan: {F88331}
Reviewers: btrahan, hach-que, lifeihuang
Reviewed By: btrahan
CC: hach-que, lifeihuang, aran, chad
Differential Revision: https://secure.phabricator.com/D7747
2013-12-09 22:22:22 +01:00
|
|
|
|
|
|
|
// 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());
|
Improve Daemon console UI
Summary:
Makes various fixes to the Daemon console UI:
- Removes timeline, timeline cursors, and timeline-related controllers. This abstraction is all but dead and just waiting on an eventual cleanup effort with Facebook (see T2003). There's no need to inspect or debug it anymore.
- Instead of showing the 15 most recently launched non-exited daemons, show all the running daemons. With the old rule, "dead" daemons tended to build up at the bottom of the list -- e.g., secure.phabricator.com shows the 7 active daemons, then 8 dead daemons from as far back as Aug 2012. Showing running daemons is far more useful.
- Simplify the two "Running Daemons" and "All Daemons" subviews into one "All Daemons" subview. The main console now has "running daemons", effectively.
- Create a "Recently completed tasks" view, which shows how many tasks of each task class have completed in the last 15 minutes and how long they took on average. Understanding how quickly tasks are completing is one of the most common uses of the daemon console, and it's currently almost useless for that. Now that we archive tasks, we can show this information in an easily digestable form.
- Partially modernize all of the remaining views.
Test Plan: Looked at daemon console.
Reviewers: btrahan, chad, vrana
Reviewed By: btrahan
CC: aran
Maniphest Tasks: T2372, T2003
Differential Revision: https://secure.phabricator.com/D4573
2013-01-22 21:14:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$completed_info = isort($completed_info, 'n');
|
|
|
|
|
|
|
|
$rows = array();
|
|
|
|
foreach ($completed_info as $class => $info) {
|
|
|
|
$rows[] = array(
|
2013-02-13 23:50:15 +01:00
|
|
|
$class,
|
Improve Daemon console UI
Summary:
Makes various fixes to the Daemon console UI:
- Removes timeline, timeline cursors, and timeline-related controllers. This abstraction is all but dead and just waiting on an eventual cleanup effort with Facebook (see T2003). There's no need to inspect or debug it anymore.
- Instead of showing the 15 most recently launched non-exited daemons, show all the running daemons. With the old rule, "dead" daemons tended to build up at the bottom of the list -- e.g., secure.phabricator.com shows the 7 active daemons, then 8 dead daemons from as far back as Aug 2012. Showing running daemons is far more useful.
- Simplify the two "Running Daemons" and "All Daemons" subviews into one "All Daemons" subview. The main console now has "running daemons", effectively.
- Create a "Recently completed tasks" view, which shows how many tasks of each task class have completed in the last 15 minutes and how long they took on average. Understanding how quickly tasks are completing is one of the most common uses of the daemon console, and it's currently almost useless for that. Now that we archive tasks, we can show this information in an easily digestable form.
- Partially modernize all of the remaining views.
Test Plan: Looked at daemon console.
Reviewers: btrahan, chad, vrana
Reviewed By: btrahan
CC: aran
Maniphest Tasks: T2372, T2003
Differential Revision: https://secure.phabricator.com/D4573
2013-01-22 21:14:33 +01:00
|
|
|
number_format($info['n']),
|
2015-05-23 14:36:02 +02:00
|
|
|
pht('%s us', new PhutilNumber((int)($info['duration'] / $info['n']))),
|
Improve Daemon console UI
Summary:
Makes various fixes to the Daemon console UI:
- Removes timeline, timeline cursors, and timeline-related controllers. This abstraction is all but dead and just waiting on an eventual cleanup effort with Facebook (see T2003). There's no need to inspect or debug it anymore.
- Instead of showing the 15 most recently launched non-exited daemons, show all the running daemons. With the old rule, "dead" daemons tended to build up at the bottom of the list -- e.g., secure.phabricator.com shows the 7 active daemons, then 8 dead daemons from as far back as Aug 2012. Showing running daemons is far more useful.
- Simplify the two "Running Daemons" and "All Daemons" subviews into one "All Daemons" subview. The main console now has "running daemons", effectively.
- Create a "Recently completed tasks" view, which shows how many tasks of each task class have completed in the last 15 minutes and how long they took on average. Understanding how quickly tasks are completing is one of the most common uses of the daemon console, and it's currently almost useless for that. Now that we archive tasks, we can show this information in an easily digestable form.
- Partially modernize all of the remaining views.
Test Plan: Looked at daemon console.
Reviewers: btrahan, chad, vrana
Reviewed By: btrahan
CC: aran
Maniphest Tasks: T2372, T2003
Differential Revision: https://secure.phabricator.com/D4573
2013-01-22 21:14:33 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2013-07-23 23:30:16 +02:00
|
|
|
if ($failed) {
|
Show a queue utilization statistic in the Daemon console
Summary:
This came up recently in a discussion with @lifeihuang, and then tangentally with @hach-que. Make it easier for users to get a sense of whether they might need to add more daemons. Although we've improved the transparency of daemons, it's not easy for non-experts to determine at a glance how close to overflowing the queue is.
This number is approximate, but should be good enough for determining if your queue is more like 25% or 95% full.
If this goes over, say, 80%, it's probably a good idea to think about adding a couple of daemons. If it's under that, you should generally be fine.
Test Plan: {F88331}
Reviewers: btrahan, hach-que, lifeihuang
Reviewed By: btrahan
CC: hach-que, lifeihuang, aran, chad
Differential Revision: https://secure.phabricator.com/D7747
2013-12-09 22:22:22 +01:00
|
|
|
// Add the time it takes to restart the daemons. This includes a guess
|
|
|
|
// about other overhead of 2X.
|
2015-02-22 16:11:43 +01:00
|
|
|
$restart_delay = PhutilDaemonHandle::getWaitBeforeRestart();
|
|
|
|
$usage_total += $restart_delay * count($failed) * 2;
|
Show a queue utilization statistic in the Daemon console
Summary:
This came up recently in a discussion with @lifeihuang, and then tangentally with @hach-que. Make it easier for users to get a sense of whether they might need to add more daemons. Although we've improved the transparency of daemons, it's not easy for non-experts to determine at a glance how close to overflowing the queue is.
This number is approximate, but should be good enough for determining if your queue is more like 25% or 95% full.
If this goes over, say, 80%, it's probably a good idea to think about adding a couple of daemons. If it's under that, you should generally be fine.
Test Plan: {F88331}
Reviewers: btrahan, hach-que, lifeihuang
Reviewed By: btrahan
CC: hach-que, lifeihuang, aran, chad
Differential Revision: https://secure.phabricator.com/D7747
2013-12-09 22:22:22 +01:00
|
|
|
foreach ($failed as $failed_task) {
|
|
|
|
$usage_start = min($usage_start, $failed_task->getFailureTime());
|
|
|
|
}
|
|
|
|
|
2013-07-23 23:30:16 +02:00
|
|
|
$rows[] = array(
|
|
|
|
phutil_tag('em', array(), pht('Temporary Failures')),
|
|
|
|
count($failed),
|
|
|
|
null,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
Show a queue utilization statistic in the Daemon console
Summary:
This came up recently in a discussion with @lifeihuang, and then tangentally with @hach-que. Make it easier for users to get a sense of whether they might need to add more daemons. Although we've improved the transparency of daemons, it's not easy for non-experts to determine at a glance how close to overflowing the queue is.
This number is approximate, but should be good enough for determining if your queue is more like 25% or 95% full.
If this goes over, say, 80%, it's probably a good idea to think about adding a couple of daemons. If it's under that, you should generally be fine.
Test Plan: {F88331}
Reviewers: btrahan, hach-que, lifeihuang
Reviewed By: btrahan
CC: hach-que, lifeihuang, aran, chad
Differential Revision: https://secure.phabricator.com/D7747
2013-12-09 22:22:22 +01:00
|
|
|
$logs = id(new PhabricatorDaemonLogQuery())
|
2015-01-20 22:32:43 +01:00
|
|
|
->setViewer($viewer)
|
Show a queue utilization statistic in the Daemon console
Summary:
This came up recently in a discussion with @lifeihuang, and then tangentally with @hach-que. Make it easier for users to get a sense of whether they might need to add more daemons. Although we've improved the transparency of daemons, it's not easy for non-experts to determine at a glance how close to overflowing the queue is.
This number is approximate, but should be good enough for determining if your queue is more like 25% or 95% full.
If this goes over, say, 80%, it's probably a good idea to think about adding a couple of daemons. If it's under that, you should generally be fine.
Test Plan: {F88331}
Reviewers: btrahan, hach-que, lifeihuang
Reviewed By: btrahan
CC: hach-que, lifeihuang, aran, chad
Differential Revision: https://secure.phabricator.com/D7747
2013-12-09 22:22:22 +01:00
|
|
|
->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE)
|
2014-01-21 23:04:12 +01:00
|
|
|
->setAllowStatusWrites(true)
|
Show a queue utilization statistic in the Daemon console
Summary:
This came up recently in a discussion with @lifeihuang, and then tangentally with @hach-que. Make it easier for users to get a sense of whether they might need to add more daemons. Although we've improved the transparency of daemons, it's not easy for non-experts to determine at a glance how close to overflowing the queue is.
This number is approximate, but should be good enough for determining if your queue is more like 25% or 95% full.
If this goes over, say, 80%, it's probably a good idea to think about adding a couple of daemons. If it's under that, you should generally be fine.
Test Plan: {F88331}
Reviewers: btrahan, hach-que, lifeihuang
Reviewed By: btrahan
CC: hach-que, lifeihuang, aran, chad
Differential Revision: https://secure.phabricator.com/D7747
2013-12-09 22:22:22 +01:00
|
|
|
->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,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
Improve Daemon console UI
Summary:
Makes various fixes to the Daemon console UI:
- Removes timeline, timeline cursors, and timeline-related controllers. This abstraction is all but dead and just waiting on an eventual cleanup effort with Facebook (see T2003). There's no need to inspect or debug it anymore.
- Instead of showing the 15 most recently launched non-exited daemons, show all the running daemons. With the old rule, "dead" daemons tended to build up at the bottom of the list -- e.g., secure.phabricator.com shows the 7 active daemons, then 8 dead daemons from as far back as Aug 2012. Showing running daemons is far more useful.
- Simplify the two "Running Daemons" and "All Daemons" subviews into one "All Daemons" subview. The main console now has "running daemons", effectively.
- Create a "Recently completed tasks" view, which shows how many tasks of each task class have completed in the last 15 minutes and how long they took on average. Understanding how quickly tasks are completing is one of the most common uses of the daemon console, and it's currently almost useless for that. Now that we archive tasks, we can show this information in an easily digestable form.
- Partially modernize all of the remaining views.
Test Plan: Looked at daemon console.
Reviewers: btrahan, chad, vrana
Reviewed By: btrahan
CC: aran
Maniphest Tasks: T2372, T2003
Differential Revision: https://secure.phabricator.com/D4573
2013-01-22 21:14:33 +01:00
|
|
|
$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',
|
|
|
|
));
|
|
|
|
|
2016-04-02 19:18:52 +02:00
|
|
|
$completed_panel = id(new PHUIObjectBoxView())
|
|
|
|
->setHeaderText(pht('Recently Completed Tasks (Last 15m)'))
|
|
|
|
->setTable($completed_table);
|
2013-07-23 21:10:30 +02:00
|
|
|
|
2016-04-15 21:00:34 +02:00
|
|
|
$daemon_table = id(new PhabricatorDaemonLogListView())
|
|
|
|
->setUser($viewer)
|
|
|
|
->setDaemonLogs($logs);
|
2014-01-20 21:08:09 +01:00
|
|
|
|
2016-04-15 21:00:34 +02:00
|
|
|
$daemon_panel = id(new PHUIObjectBoxView())
|
|
|
|
->setHeaderText(pht('Active Daemons'))
|
|
|
|
->setTable($daemon_table);
|
2011-03-11 18:34:22 +01:00
|
|
|
|
2014-12-31 00:54:56 +01:00
|
|
|
$tasks = id(new PhabricatorWorkerLeaseQuery())
|
|
|
|
->setSkipLease(true)
|
|
|
|
->withLeasedTasks(true)
|
|
|
|
->setLimit(100)
|
|
|
|
->execute();
|
2014-10-31 17:27:04 +01:00
|
|
|
|
2014-12-30 19:00:06 +01:00
|
|
|
$tasks_table = id(new PhabricatorDaemonTasksTableView())
|
|
|
|
->setTasks($tasks)
|
|
|
|
->setNoDataString(pht('No tasks are leased by workers.'));
|
2014-10-31 17:27:04 +01:00
|
|
|
|
|
|
|
$leased_panel = id(new PHUIObjectBoxView())
|
|
|
|
->setHeaderText(pht('Leased Tasks'))
|
2015-05-28 20:13:18 +02:00
|
|
|
->setTable($tasks_table);
|
2011-03-11 18:34:22 +01:00
|
|
|
|
2012-10-31 23:22:16 +01:00
|
|
|
$task_table = new PhabricatorWorkerActiveTask();
|
2011-03-11 18:34:22 +01:00
|
|
|
$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(
|
2013-02-13 23:50:15 +01:00
|
|
|
$row['taskClass'],
|
2011-03-11 18:34:22 +01:00
|
|
|
number_format($row['N']),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
$queued_table = new AphrontTableView($rows);
|
|
|
|
$queued_table->setHeaders(
|
|
|
|
array(
|
2013-02-27 04:51:36 +01:00
|
|
|
pht('Class'),
|
|
|
|
pht('Count'),
|
2011-03-10 22:48:29 +01:00
|
|
|
));
|
2011-03-11 18:34:22 +01:00
|
|
|
$queued_table->setColumnClasses(
|
|
|
|
array(
|
|
|
|
'wide',
|
|
|
|
'n',
|
|
|
|
));
|
2013-02-27 04:51:36 +01:00
|
|
|
$queued_table->setNoDataString(pht('Task queue is empty.'));
|
2011-03-11 18:34:22 +01:00
|
|
|
|
2014-01-20 21:08:09 +01:00
|
|
|
$queued_panel = new PHUIObjectBoxView();
|
|
|
|
$queued_panel->setHeaderText(pht('Queued Tasks'));
|
2015-05-28 20:13:18 +02:00
|
|
|
$queued_panel->setTable($queued_table);
|
2011-03-10 22:48:29 +01:00
|
|
|
|
2014-10-31 17:27:04 +01:00
|
|
|
$upcoming = id(new PhabricatorWorkerLeaseQuery())
|
|
|
|
->setLimit(10)
|
|
|
|
->setSkipLease(true)
|
|
|
|
->execute();
|
|
|
|
|
|
|
|
$upcoming_panel = id(new PHUIObjectBoxView())
|
|
|
|
->setHeaderText(pht('Next In Queue'))
|
2015-05-28 20:13:18 +02:00
|
|
|
->setTable(
|
2014-12-30 19:00:06 +01:00
|
|
|
id(new PhabricatorDaemonTasksTableView())
|
|
|
|
->setTasks($upcoming)
|
|
|
|
->setNoDataString(pht('Task queue is empty.')));
|
2014-10-31 17:27:04 +01:00
|
|
|
|
Implement clock/trigger infrastructure for scheduling actions
Summary:
Ref T6881. Hopefully, this is the hard part.
This adds a new daemon (the "trigger" daemon) which processes triggers, schedules them, and then executes them at the scheduled time. The design is a little complicated, but has these goals:
- High resistance to race conditions: only the application writes to the trigger table; only the daemon writes to the event table. We won't lose events if someone saves a meeting at the same time as we're sending a reminder out for it.
- Execution guarantees: scheduled events are guaranteed to execute exactly once.
- Support for arbitrarily large queues: the daemon will make progress even if there are millions of triggers in queue. The cost to update the queue is proportional to the number of changes in it; the cost to process the queue is proportional to the number of events to execute.
- Relatively good observability: you can monitor the state of the trigger queue reasonably well from the web UI.
- Modular Infrastructure: this is a very low-level construct that Calendar, Phortune, etc., should be able to build on top of.
It doesn't have this stuff yet:
- Not very robust to bad actions: a misbehaving trigger can stop the queue fairly easily. This is OK for now since we aren't planning to make it part of any other applications for a while. We do still get execute-exaclty-once, but it might not happen for a long time (until someone goes and fixes the queue), when we could theoretically continue executing other events.
- Doesn't start automatically: normal users don't need to run this thing yet so I'm not starting it by default.
- Not super well tested: I've vetted the basics but haven't run real workloads through this yet.
- No sophisticated tooling: I added some basic stuff but it's missing some pieces we'll have to build sooner or later, e.g. `bin/trigger cancel` or whatever.
- Intentionally not realtime: This design puts execution guarantees far above realtime concerns, and will not give you precise event execution at 1-second resolution. I think this is the correct goal to pursue architecturally, and certainly correct for subscriptions and meeting reminders. Events which execute after they have become irrelevant can simply decline to do anything (like a meeting reminder which executes after the meeting is over).
In general, the expectation for applications is:
- When creating an object (like a calendar event) that needs to trigger a scheduled action, write a trigger (and save the PHID if you plan to update it later).
- The daemon will process the event and schedule the action efficiently, in a race-free way.
- If you want to move the action, update the trigger and the daemon will take care of it.
- Your action will eventually dump a task into the task queue, and the task daemons will actually perform it.
Test Plan:
Using a test script like this:
```
<?php
require_once 'scripts/__init_script__.php';
$trigger = id(new PhabricatorWorkerTrigger())
->setAction(
new PhabricatorLogTriggerAction(
array(
'message' => 'test',
)))
->setClock(
new PhabricatorMetronomicTriggerClock(
array(
'period' => 33,
)))
->save();
var_dump($trigger);
```
...I queued triggers and ran the daemon:
- Verified triggers fire;
- verified triggers reschedule;
- verified trigger events show up in the web UI;
- tried different periods;
- added some triggers while the daemon was running;
- examined `phd debug` output for anything suspicious.
It seems to work in trivial use case, at least.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: epriestley
Maniphest Tasks: T6881
Differential Revision: https://secure.phabricator.com/D11419
2015-01-16 21:13:31 +01:00
|
|
|
$triggers = id(new PhabricatorWorkerTriggerQuery())
|
2015-01-20 22:32:43 +01:00
|
|
|
->setViewer($viewer)
|
Implement clock/trigger infrastructure for scheduling actions
Summary:
Ref T6881. Hopefully, this is the hard part.
This adds a new daemon (the "trigger" daemon) which processes triggers, schedules them, and then executes them at the scheduled time. The design is a little complicated, but has these goals:
- High resistance to race conditions: only the application writes to the trigger table; only the daemon writes to the event table. We won't lose events if someone saves a meeting at the same time as we're sending a reminder out for it.
- Execution guarantees: scheduled events are guaranteed to execute exactly once.
- Support for arbitrarily large queues: the daemon will make progress even if there are millions of triggers in queue. The cost to update the queue is proportional to the number of changes in it; the cost to process the queue is proportional to the number of events to execute.
- Relatively good observability: you can monitor the state of the trigger queue reasonably well from the web UI.
- Modular Infrastructure: this is a very low-level construct that Calendar, Phortune, etc., should be able to build on top of.
It doesn't have this stuff yet:
- Not very robust to bad actions: a misbehaving trigger can stop the queue fairly easily. This is OK for now since we aren't planning to make it part of any other applications for a while. We do still get execute-exaclty-once, but it might not happen for a long time (until someone goes and fixes the queue), when we could theoretically continue executing other events.
- Doesn't start automatically: normal users don't need to run this thing yet so I'm not starting it by default.
- Not super well tested: I've vetted the basics but haven't run real workloads through this yet.
- No sophisticated tooling: I added some basic stuff but it's missing some pieces we'll have to build sooner or later, e.g. `bin/trigger cancel` or whatever.
- Intentionally not realtime: This design puts execution guarantees far above realtime concerns, and will not give you precise event execution at 1-second resolution. I think this is the correct goal to pursue architecturally, and certainly correct for subscriptions and meeting reminders. Events which execute after they have become irrelevant can simply decline to do anything (like a meeting reminder which executes after the meeting is over).
In general, the expectation for applications is:
- When creating an object (like a calendar event) that needs to trigger a scheduled action, write a trigger (and save the PHID if you plan to update it later).
- The daemon will process the event and schedule the action efficiently, in a race-free way.
- If you want to move the action, update the trigger and the daemon will take care of it.
- Your action will eventually dump a task into the task queue, and the task daemons will actually perform it.
Test Plan:
Using a test script like this:
```
<?php
require_once 'scripts/__init_script__.php';
$trigger = id(new PhabricatorWorkerTrigger())
->setAction(
new PhabricatorLogTriggerAction(
array(
'message' => 'test',
)))
->setClock(
new PhabricatorMetronomicTriggerClock(
array(
'period' => 33,
)))
->save();
var_dump($trigger);
```
...I queued triggers and ran the daemon:
- Verified triggers fire;
- verified triggers reschedule;
- verified trigger events show up in the web UI;
- tried different periods;
- added some triggers while the daemon was running;
- examined `phd debug` output for anything suspicious.
It seems to work in trivial use case, at least.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: epriestley
Maniphest Tasks: T6881
Differential Revision: https://secure.phabricator.com/D11419
2015-01-16 21:13:31 +01:00
|
|
|
->setOrder(PhabricatorWorkerTriggerQuery::ORDER_EXECUTION)
|
|
|
|
->needEvents(true)
|
|
|
|
->setLimit(10)
|
|
|
|
->execute();
|
|
|
|
|
|
|
|
$triggers_table = $this->buildTriggersTable($triggers);
|
|
|
|
|
|
|
|
$triggers_panel = id(new PHUIObjectBoxView())
|
|
|
|
->setHeaderText(pht('Upcoming Triggers'))
|
2015-05-28 20:13:18 +02:00
|
|
|
->setTable($triggers_table);
|
Implement clock/trigger infrastructure for scheduling actions
Summary:
Ref T6881. Hopefully, this is the hard part.
This adds a new daemon (the "trigger" daemon) which processes triggers, schedules them, and then executes them at the scheduled time. The design is a little complicated, but has these goals:
- High resistance to race conditions: only the application writes to the trigger table; only the daemon writes to the event table. We won't lose events if someone saves a meeting at the same time as we're sending a reminder out for it.
- Execution guarantees: scheduled events are guaranteed to execute exactly once.
- Support for arbitrarily large queues: the daemon will make progress even if there are millions of triggers in queue. The cost to update the queue is proportional to the number of changes in it; the cost to process the queue is proportional to the number of events to execute.
- Relatively good observability: you can monitor the state of the trigger queue reasonably well from the web UI.
- Modular Infrastructure: this is a very low-level construct that Calendar, Phortune, etc., should be able to build on top of.
It doesn't have this stuff yet:
- Not very robust to bad actions: a misbehaving trigger can stop the queue fairly easily. This is OK for now since we aren't planning to make it part of any other applications for a while. We do still get execute-exaclty-once, but it might not happen for a long time (until someone goes and fixes the queue), when we could theoretically continue executing other events.
- Doesn't start automatically: normal users don't need to run this thing yet so I'm not starting it by default.
- Not super well tested: I've vetted the basics but haven't run real workloads through this yet.
- No sophisticated tooling: I added some basic stuff but it's missing some pieces we'll have to build sooner or later, e.g. `bin/trigger cancel` or whatever.
- Intentionally not realtime: This design puts execution guarantees far above realtime concerns, and will not give you precise event execution at 1-second resolution. I think this is the correct goal to pursue architecturally, and certainly correct for subscriptions and meeting reminders. Events which execute after they have become irrelevant can simply decline to do anything (like a meeting reminder which executes after the meeting is over).
In general, the expectation for applications is:
- When creating an object (like a calendar event) that needs to trigger a scheduled action, write a trigger (and save the PHID if you plan to update it later).
- The daemon will process the event and schedule the action efficiently, in a race-free way.
- If you want to move the action, update the trigger and the daemon will take care of it.
- Your action will eventually dump a task into the task queue, and the task daemons will actually perform it.
Test Plan:
Using a test script like this:
```
<?php
require_once 'scripts/__init_script__.php';
$trigger = id(new PhabricatorWorkerTrigger())
->setAction(
new PhabricatorLogTriggerAction(
array(
'message' => 'test',
)))
->setClock(
new PhabricatorMetronomicTriggerClock(
array(
'period' => 33,
)))
->save();
var_dump($trigger);
```
...I queued triggers and ran the daemon:
- Verified triggers fire;
- verified triggers reschedule;
- verified trigger events show up in the web UI;
- tried different periods;
- added some triggers while the daemon was running;
- examined `phd debug` output for anything suspicious.
It seems to work in trivial use case, at least.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: epriestley
Maniphest Tasks: T6881
Differential Revision: https://secure.phabricator.com/D11419
2015-01-16 21:13:31 +01:00
|
|
|
|
2013-07-23 23:30:16 +02:00
|
|
|
$crumbs = $this->buildApplicationCrumbs();
|
2013-12-19 02:47:34 +01:00
|
|
|
$crumbs->addTextCrumb(pht('Console'));
|
2013-07-23 23:30:16 +02:00
|
|
|
|
2012-08-14 00:27:45 +02:00
|
|
|
$nav = $this->buildSideNavView();
|
Improve Daemon console UI
Summary:
Makes various fixes to the Daemon console UI:
- Removes timeline, timeline cursors, and timeline-related controllers. This abstraction is all but dead and just waiting on an eventual cleanup effort with Facebook (see T2003). There's no need to inspect or debug it anymore.
- Instead of showing the 15 most recently launched non-exited daemons, show all the running daemons. With the old rule, "dead" daemons tended to build up at the bottom of the list -- e.g., secure.phabricator.com shows the 7 active daemons, then 8 dead daemons from as far back as Aug 2012. Showing running daemons is far more useful.
- Simplify the two "Running Daemons" and "All Daemons" subviews into one "All Daemons" subview. The main console now has "running daemons", effectively.
- Create a "Recently completed tasks" view, which shows how many tasks of each task class have completed in the last 15 minutes and how long they took on average. Understanding how quickly tasks are completing is one of the most common uses of the daemon console, and it's currently almost useless for that. Now that we archive tasks, we can show this information in an easily digestable form.
- Partially modernize all of the remaining views.
Test Plan: Looked at daemon console.
Reviewers: btrahan, chad, vrana
Reviewed By: btrahan
CC: aran
Maniphest Tasks: T2372, T2003
Differential Revision: https://secure.phabricator.com/D4573
2013-01-22 21:14:33 +01:00
|
|
|
$nav->selectFilter('/');
|
2012-08-14 00:27:45 +02:00
|
|
|
$nav->appendChild(
|
2011-03-11 18:34:22 +01:00
|
|
|
array(
|
2013-07-23 23:30:16 +02:00
|
|
|
$crumbs,
|
Improve Daemon console UI
Summary:
Makes various fixes to the Daemon console UI:
- Removes timeline, timeline cursors, and timeline-related controllers. This abstraction is all but dead and just waiting on an eventual cleanup effort with Facebook (see T2003). There's no need to inspect or debug it anymore.
- Instead of showing the 15 most recently launched non-exited daemons, show all the running daemons. With the old rule, "dead" daemons tended to build up at the bottom of the list -- e.g., secure.phabricator.com shows the 7 active daemons, then 8 dead daemons from as far back as Aug 2012. Showing running daemons is far more useful.
- Simplify the two "Running Daemons" and "All Daemons" subviews into one "All Daemons" subview. The main console now has "running daemons", effectively.
- Create a "Recently completed tasks" view, which shows how many tasks of each task class have completed in the last 15 minutes and how long they took on average. Understanding how quickly tasks are completing is one of the most common uses of the daemon console, and it's currently almost useless for that. Now that we archive tasks, we can show this information in an easily digestable form.
- Partially modernize all of the remaining views.
Test Plan: Looked at daemon console.
Reviewers: btrahan, chad, vrana
Reviewed By: btrahan
CC: aran
Maniphest Tasks: T2372, T2003
Differential Revision: https://secure.phabricator.com/D4573
2013-01-22 21:14:33 +01:00
|
|
|
$completed_panel,
|
2014-01-20 21:08:09 +01:00
|
|
|
$daemon_panel,
|
2012-04-13 11:17:34 +02:00
|
|
|
$queued_panel,
|
|
|
|
$leased_panel,
|
2014-10-31 17:27:04 +01:00
|
|
|
$upcoming_panel,
|
Implement clock/trigger infrastructure for scheduling actions
Summary:
Ref T6881. Hopefully, this is the hard part.
This adds a new daemon (the "trigger" daemon) which processes triggers, schedules them, and then executes them at the scheduled time. The design is a little complicated, but has these goals:
- High resistance to race conditions: only the application writes to the trigger table; only the daemon writes to the event table. We won't lose events if someone saves a meeting at the same time as we're sending a reminder out for it.
- Execution guarantees: scheduled events are guaranteed to execute exactly once.
- Support for arbitrarily large queues: the daemon will make progress even if there are millions of triggers in queue. The cost to update the queue is proportional to the number of changes in it; the cost to process the queue is proportional to the number of events to execute.
- Relatively good observability: you can monitor the state of the trigger queue reasonably well from the web UI.
- Modular Infrastructure: this is a very low-level construct that Calendar, Phortune, etc., should be able to build on top of.
It doesn't have this stuff yet:
- Not very robust to bad actions: a misbehaving trigger can stop the queue fairly easily. This is OK for now since we aren't planning to make it part of any other applications for a while. We do still get execute-exaclty-once, but it might not happen for a long time (until someone goes and fixes the queue), when we could theoretically continue executing other events.
- Doesn't start automatically: normal users don't need to run this thing yet so I'm not starting it by default.
- Not super well tested: I've vetted the basics but haven't run real workloads through this yet.
- No sophisticated tooling: I added some basic stuff but it's missing some pieces we'll have to build sooner or later, e.g. `bin/trigger cancel` or whatever.
- Intentionally not realtime: This design puts execution guarantees far above realtime concerns, and will not give you precise event execution at 1-second resolution. I think this is the correct goal to pursue architecturally, and certainly correct for subscriptions and meeting reminders. Events which execute after they have become irrelevant can simply decline to do anything (like a meeting reminder which executes after the meeting is over).
In general, the expectation for applications is:
- When creating an object (like a calendar event) that needs to trigger a scheduled action, write a trigger (and save the PHID if you plan to update it later).
- The daemon will process the event and schedule the action efficiently, in a race-free way.
- If you want to move the action, update the trigger and the daemon will take care of it.
- Your action will eventually dump a task into the task queue, and the task daemons will actually perform it.
Test Plan:
Using a test script like this:
```
<?php
require_once 'scripts/__init_script__.php';
$trigger = id(new PhabricatorWorkerTrigger())
->setAction(
new PhabricatorLogTriggerAction(
array(
'message' => 'test',
)))
->setClock(
new PhabricatorMetronomicTriggerClock(
array(
'period' => 33,
)))
->save();
var_dump($trigger);
```
...I queued triggers and ran the daemon:
- Verified triggers fire;
- verified triggers reschedule;
- verified trigger events show up in the web UI;
- tried different periods;
- added some triggers while the daemon was running;
- examined `phd debug` output for anything suspicious.
It seems to work in trivial use case, at least.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: epriestley
Maniphest Tasks: T6881
Differential Revision: https://secure.phabricator.com/D11419
2015-01-16 21:13:31 +01:00
|
|
|
$triggers_panel,
|
2012-08-14 00:27:45 +02:00
|
|
|
));
|
|
|
|
|
2016-04-02 19:18:52 +02:00
|
|
|
return $this->newPage()
|
|
|
|
->setTitle(pht('Console'))
|
|
|
|
->appendChild($nav);
|
|
|
|
|
2011-03-10 22:48:29 +01:00
|
|
|
}
|
|
|
|
|
Implement clock/trigger infrastructure for scheduling actions
Summary:
Ref T6881. Hopefully, this is the hard part.
This adds a new daemon (the "trigger" daemon) which processes triggers, schedules them, and then executes them at the scheduled time. The design is a little complicated, but has these goals:
- High resistance to race conditions: only the application writes to the trigger table; only the daemon writes to the event table. We won't lose events if someone saves a meeting at the same time as we're sending a reminder out for it.
- Execution guarantees: scheduled events are guaranteed to execute exactly once.
- Support for arbitrarily large queues: the daemon will make progress even if there are millions of triggers in queue. The cost to update the queue is proportional to the number of changes in it; the cost to process the queue is proportional to the number of events to execute.
- Relatively good observability: you can monitor the state of the trigger queue reasonably well from the web UI.
- Modular Infrastructure: this is a very low-level construct that Calendar, Phortune, etc., should be able to build on top of.
It doesn't have this stuff yet:
- Not very robust to bad actions: a misbehaving trigger can stop the queue fairly easily. This is OK for now since we aren't planning to make it part of any other applications for a while. We do still get execute-exaclty-once, but it might not happen for a long time (until someone goes and fixes the queue), when we could theoretically continue executing other events.
- Doesn't start automatically: normal users don't need to run this thing yet so I'm not starting it by default.
- Not super well tested: I've vetted the basics but haven't run real workloads through this yet.
- No sophisticated tooling: I added some basic stuff but it's missing some pieces we'll have to build sooner or later, e.g. `bin/trigger cancel` or whatever.
- Intentionally not realtime: This design puts execution guarantees far above realtime concerns, and will not give you precise event execution at 1-second resolution. I think this is the correct goal to pursue architecturally, and certainly correct for subscriptions and meeting reminders. Events which execute after they have become irrelevant can simply decline to do anything (like a meeting reminder which executes after the meeting is over).
In general, the expectation for applications is:
- When creating an object (like a calendar event) that needs to trigger a scheduled action, write a trigger (and save the PHID if you plan to update it later).
- The daemon will process the event and schedule the action efficiently, in a race-free way.
- If you want to move the action, update the trigger and the daemon will take care of it.
- Your action will eventually dump a task into the task queue, and the task daemons will actually perform it.
Test Plan:
Using a test script like this:
```
<?php
require_once 'scripts/__init_script__.php';
$trigger = id(new PhabricatorWorkerTrigger())
->setAction(
new PhabricatorLogTriggerAction(
array(
'message' => 'test',
)))
->setClock(
new PhabricatorMetronomicTriggerClock(
array(
'period' => 33,
)))
->save();
var_dump($trigger);
```
...I queued triggers and ran the daemon:
- Verified triggers fire;
- verified triggers reschedule;
- verified trigger events show up in the web UI;
- tried different periods;
- added some triggers while the daemon was running;
- examined `phd debug` output for anything suspicious.
It seems to work in trivial use case, at least.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: epriestley
Maniphest Tasks: T6881
Differential Revision: https://secure.phabricator.com/D11419
2015-01-16 21:13:31 +01:00
|
|
|
private function buildTriggersTable(array $triggers) {
|
|
|
|
$viewer = $this->getViewer();
|
|
|
|
|
|
|
|
$rows = array();
|
|
|
|
foreach ($triggers as $trigger) {
|
|
|
|
$event = $trigger->getEvent();
|
|
|
|
if ($event) {
|
|
|
|
$last_epoch = $event->getLastEventEpoch();
|
|
|
|
$next_epoch = $event->getNextEventEpoch();
|
|
|
|
} else {
|
|
|
|
$last_epoch = null;
|
|
|
|
$next_epoch = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$rows[] = array(
|
|
|
|
$trigger->getID(),
|
|
|
|
$trigger->getClockClass(),
|
|
|
|
$trigger->getActionClass(),
|
|
|
|
$last_epoch ? phabricator_datetime($last_epoch, $viewer) : null,
|
|
|
|
$next_epoch ? phabricator_datetime($next_epoch, $viewer) : null,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return id(new AphrontTableView($rows))
|
|
|
|
->setNoDataString(pht('There are no upcoming event triggers.'))
|
|
|
|
->setHeaders(
|
|
|
|
array(
|
2015-03-03 20:11:26 +01:00
|
|
|
pht('ID'),
|
|
|
|
pht('Clock'),
|
|
|
|
pht('Action'),
|
|
|
|
pht('Last'),
|
|
|
|
pht('Next'),
|
Implement clock/trigger infrastructure for scheduling actions
Summary:
Ref T6881. Hopefully, this is the hard part.
This adds a new daemon (the "trigger" daemon) which processes triggers, schedules them, and then executes them at the scheduled time. The design is a little complicated, but has these goals:
- High resistance to race conditions: only the application writes to the trigger table; only the daemon writes to the event table. We won't lose events if someone saves a meeting at the same time as we're sending a reminder out for it.
- Execution guarantees: scheduled events are guaranteed to execute exactly once.
- Support for arbitrarily large queues: the daemon will make progress even if there are millions of triggers in queue. The cost to update the queue is proportional to the number of changes in it; the cost to process the queue is proportional to the number of events to execute.
- Relatively good observability: you can monitor the state of the trigger queue reasonably well from the web UI.
- Modular Infrastructure: this is a very low-level construct that Calendar, Phortune, etc., should be able to build on top of.
It doesn't have this stuff yet:
- Not very robust to bad actions: a misbehaving trigger can stop the queue fairly easily. This is OK for now since we aren't planning to make it part of any other applications for a while. We do still get execute-exaclty-once, but it might not happen for a long time (until someone goes and fixes the queue), when we could theoretically continue executing other events.
- Doesn't start automatically: normal users don't need to run this thing yet so I'm not starting it by default.
- Not super well tested: I've vetted the basics but haven't run real workloads through this yet.
- No sophisticated tooling: I added some basic stuff but it's missing some pieces we'll have to build sooner or later, e.g. `bin/trigger cancel` or whatever.
- Intentionally not realtime: This design puts execution guarantees far above realtime concerns, and will not give you precise event execution at 1-second resolution. I think this is the correct goal to pursue architecturally, and certainly correct for subscriptions and meeting reminders. Events which execute after they have become irrelevant can simply decline to do anything (like a meeting reminder which executes after the meeting is over).
In general, the expectation for applications is:
- When creating an object (like a calendar event) that needs to trigger a scheduled action, write a trigger (and save the PHID if you plan to update it later).
- The daemon will process the event and schedule the action efficiently, in a race-free way.
- If you want to move the action, update the trigger and the daemon will take care of it.
- Your action will eventually dump a task into the task queue, and the task daemons will actually perform it.
Test Plan:
Using a test script like this:
```
<?php
require_once 'scripts/__init_script__.php';
$trigger = id(new PhabricatorWorkerTrigger())
->setAction(
new PhabricatorLogTriggerAction(
array(
'message' => 'test',
)))
->setClock(
new PhabricatorMetronomicTriggerClock(
array(
'period' => 33,
)))
->save();
var_dump($trigger);
```
...I queued triggers and ran the daemon:
- Verified triggers fire;
- verified triggers reschedule;
- verified trigger events show up in the web UI;
- tried different periods;
- added some triggers while the daemon was running;
- examined `phd debug` output for anything suspicious.
It seems to work in trivial use case, at least.
Reviewers: btrahan
Reviewed By: btrahan
Subscribers: epriestley
Maniphest Tasks: T6881
Differential Revision: https://secure.phabricator.com/D11419
2015-01-16 21:13:31 +01:00
|
|
|
))
|
|
|
|
->setColumnClasses(
|
|
|
|
array(
|
|
|
|
'',
|
|
|
|
'',
|
|
|
|
'wide',
|
|
|
|
'date',
|
|
|
|
'date',
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2011-03-10 22:48:29 +01:00
|
|
|
}
|