diff --git a/conf/default.conf.php b/conf/default.conf.php
index da5d7a0c2b..7472c1ecb0 100644
--- a/conf/default.conf.php
+++ b/conf/default.conf.php
@@ -375,6 +375,37 @@ return array(
// silly (but sort of awesome).
'remarkup.enable-embedded-youtube' => false,
+
+// -- Garbage Collection ---------------------------------------------------- //
+
+ // Phabricator generates various logs and caches in the database which can
+ // be garbage collected after a while to make the total data size more
+ // manageable. To run garbage collection, launch a
+ // PhabricatorGarbageCollector daemon.
+
+ // Since the GC daemon can issue large writes and table scans, you may want to
+ // run it only during off hours or make sure it is scheduled so it doesn't
+ // overlap with backups. This determines when the daemon can start running
+ // each day.
+ 'gcdaemon.run-at' => '12 AM',
+
+ // How many seconds after 'gcdaemon.run-at' the daemon may collect garbage
+ // for. By default it runs continuously, but you can set it to run for a
+ // limited period of time. For instance, if you do backups at 3 AM, you might
+ // run garbage collection for an hour beforehand. This is not a high-precision
+ // limit so you may want to leave some room for the GC to actually stop, and
+ // if you set it to something like 3 seconds you're on your own.
+ 'gcdaemon.run-for' => 24 * 60 * 60,
+
+ // These 'ttl' keys configure how much old data the GC daemon keeps around.
+ // Objects older than the ttl will be collected. Set any value to 0 to store
+ // data indefinitely.
+
+ 'gcdaemon.ttl.herald-transcripts' => 30 * (24 * 60 * 60),
+ 'gcdaemon.ttl.daemon-logs' => 7 * (24 * 60 * 60),
+ 'gcdaemon.ttl.differential-render-cache' => 7 * (24 * 60 * 60),
+
+
// -- Customization --------------------------------------------------------- //
// Paths to additional phutil libraries to load.
diff --git a/scripts/install/update_phabricator.sh b/scripts/install/update_phabricator.sh
index 207650d108..ebde400efe 100755
--- a/scripts/install/update_phabricator.sh
+++ b/scripts/install/update_phabricator.sh
@@ -58,5 +58,6 @@ sudo /etc/init.d/httpd start
# $ROOT/phabricator/bin/phd repository-launch-master
# $ROOT/phabricator/bin/phd launch metamta
+# $ROOT/phabricator/bin/phd launch garbagecollector
# $ROOT/phabricator/bin/phd launch 4 taskmaster
# $ROOT/phabricator/bin/phd launch ircbot /config/bot.json
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
index af7dba56bd..f38452557b 100644
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -352,6 +352,7 @@ phutil_register_library_map(array(
'PhabricatorFileURI' => 'applications/files/uri',
'PhabricatorFileUploadController' => 'applications/files/controller/upload',
'PhabricatorFileViewController' => 'applications/files/controller/view',
+ 'PhabricatorGarbageCollectorDaemon' => 'infrastructure/daemon/garbagecollector',
'PhabricatorGoodForNothingWorker' => 'infrastructure/daemon/workers/worker/goodfornothing',
'PhabricatorHandleObjectSelectorDataView' => 'applications/phid/handle/view/selector',
'PhabricatorHelpController' => 'applications/help/controller/base',
@@ -844,6 +845,7 @@ phutil_register_library_map(array(
'PhabricatorFileTransformController' => 'PhabricatorFileController',
'PhabricatorFileUploadController' => 'PhabricatorFileController',
'PhabricatorFileViewController' => 'PhabricatorFileController',
+ 'PhabricatorGarbageCollectorDaemon' => 'PhabricatorDaemon',
'PhabricatorGoodForNothingWorker' => 'PhabricatorWorker',
'PhabricatorHelpController' => 'PhabricatorController',
'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController',
diff --git a/src/applications/daemon/view/daemonlogevents/PhabricatorDaemonLogEventsView.php b/src/applications/daemon/view/daemonlogevents/PhabricatorDaemonLogEventsView.php
index dd09781683..aaebcbec0c 100644
--- a/src/applications/daemon/view/daemonlogevents/PhabricatorDaemonLogEventsView.php
+++ b/src/applications/daemon/view/daemonlogevents/PhabricatorDaemonLogEventsView.php
@@ -43,11 +43,46 @@ final class PhabricatorDaemonLogEventsView extends AphrontView {
}
foreach ($this->events as $event) {
+
+ // Limit display log size. If a daemon gets stuck in an output loop this
+ // page can be like >100MB if we don't truncate stuff. Try to do cheap
+ // line-based truncation first, and fall back to expensive UTF-8 character
+ // truncation if that doesn't get things short enough.
+
+ $message = $event->getMessage();
+
+ $more_lines = null;
+ $more_chars = null;
+ $line_limit = 12;
+ if (substr_count($message, "\n") > $line_limit) {
+ $message = explode("\n", $message);
+ $more_lines = count($message) - $line_limit;
+ $message = array_slice($message, 0, $line_limit);
+ $message = implode("\n", $message);
+ }
+
+ $char_limit = 8192;
+ if (strlen($message) > $char_limit) {
+ $message = phutil_utf8v($message);
+ $more_chars = count($message) - $char_limit;
+ $message = array_slice($message, 0, $char_limit);
+ $message = implode('', $message);
+ }
+
+ $more = null;
+ if ($more_chars) {
+ $more = number_format($more_chars);
+ $more = "\n<... {$more} more characters ...>";
+ } else if ($more_lines) {
+ $more = number_format($more_lines);
+ $more = "\n<... {$more} more lines ...>";
+ }
+
$row = array(
phutil_escape_html($event->getLogType()),
phabricator_date($event->getEpoch(), $this->user),
phabricator_time($event->getEpoch(), $this->user),
- str_replace("\n", '
', phutil_escape_html($event->getMessage())),
+ str_replace("\n", '
', phutil_escape_html($message.$more)),
);
if ($this->combinedLog) {
diff --git a/src/applications/daemon/view/daemonlogevents/__init__.php b/src/applications/daemon/view/daemonlogevents/__init__.php
index dd4e977647..04be32163d 100644
--- a/src/applications/daemon/view/daemonlogevents/__init__.php
+++ b/src/applications/daemon/view/daemonlogevents/__init__.php
@@ -11,6 +11,7 @@ phutil_require_module('phabricator', 'view/control/table');
phutil_require_module('phabricator', 'view/utils');
phutil_require_module('phutil', 'markup');
+phutil_require_module('phutil', 'utils');
phutil_require_source('PhabricatorDaemonLogEventsView.php');
diff --git a/src/applications/herald/controller/transcript/HeraldTranscriptController.php b/src/applications/herald/controller/transcript/HeraldTranscriptController.php
index ced383d98d..bf9f0fcd5d 100644
--- a/src/applications/herald/controller/transcript/HeraldTranscriptController.php
+++ b/src/applications/herald/controller/transcript/HeraldTranscriptController.php
@@ -42,40 +42,45 @@ class HeraldTranscriptController extends HeraldController {
throw new Exception('Uknown transcript!');
}
- $field_names = HeraldFieldConfig::getFieldMap();
- $condition_names = HeraldConditionConfig::getConditionMap();
- $action_names = HeraldActionConfig::getActionMap();
-
require_celerity_resource('herald-test-css');
- $filter = $this->getFilterPHIDs();
- $this->filterTranscript($xscript, $filter);
- $phids = array_merge($filter, $this->getTranscriptPHIDs($xscript));
- $phids = array_unique($phids);
- $phids = array_filter($phids);
-
- $handles = id(new PhabricatorObjectHandleData($phids))
- ->loadHandles();
- $this->handles = $handles;
-
- $object_xscript = $xscript->getObjectTranscript();
-
$nav = $this->buildSideNav();
- $apply_xscript_panel = $this->buildApplyTranscriptPanel(
- $xscript);
- $nav->appendChild($apply_xscript_panel);
+ $object_xscript = $xscript->getObjectTranscript();
+ if (!$object_xscript) {
+ $notice = id(new AphrontErrorView())
+ ->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
+ ->setTitle('Old Transcript')
+ ->appendChild(
+ '
Details of this transcript have been garbage collected.
'); + $nav->appendChild($notice); + } else { + $filter = $this->getFilterPHIDs(); + $this->filterTranscript($xscript, $filter); + $phids = array_merge($filter, $this->getTranscriptPHIDs($xscript)); + $phids = array_unique($phids); + $phids = array_filter($phids); - $action_xscript_panel = $this->buildActionTranscriptPanel( - $xscript); - $nav->appendChild($action_xscript_panel); + $handles = id(new PhabricatorObjectHandleData($phids)) + ->loadHandles(); + $this->handles = $handles; - $object_xscript_panel = $this->buildObjectTranscriptPanel( - $xscript); - $nav->appendChild($object_xscript_panel); + $apply_xscript_panel = $this->buildApplyTranscriptPanel( + $xscript); + $nav->appendChild($apply_xscript_panel); + + $action_xscript_panel = $this->buildActionTranscriptPanel( + $xscript); + $nav->appendChild($action_xscript_panel); + + $object_xscript_panel = $this->buildObjectTranscriptPanel( + $xscript); + $nav->appendChild($object_xscript_panel); + } /* + TODO $notice = null; if ($xscript->getDryRun()) { @@ -84,30 +89,6 @@ class HeraldTranscriptController extends HeraldController { This was a dry run to test Herald rules, no actions were executed. ; } - - if (!$object_xscript) { - $notice = -