diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 1e678ec..b5a99b0 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1,468 +1,470 @@ 2, 'class' => array( 'AASTNode' => 'parser/aast/api/AASTNode.php', 'AASTNodeList' => 'parser/aast/api/AASTNodeList.php', 'AASTToken' => 'parser/aast/api/AASTToken.php', 'AASTTree' => 'parser/aast/api/AASTTree.php', 'AbstractDirectedGraph' => 'utils/AbstractDirectedGraph.php', 'AbstractDirectedGraphTestCase' => 'utils/__tests__/AbstractDirectedGraphTestCase.php', 'AphrontDatabaseConnection' => 'aphront/storage/connection/AphrontDatabaseConnection.php', 'AphrontDatabaseTransactionState' => 'aphront/storage/connection/AphrontDatabaseTransactionState.php', 'AphrontIsolatedDatabaseConnection' => 'aphront/storage/connection/AphrontIsolatedDatabaseConnection.php', 'AphrontMySQLDatabaseConnection' => 'aphront/storage/connection/mysql/AphrontMySQLDatabaseConnection.php', 'AphrontMySQLDatabaseConnectionBase' => 'aphront/storage/connection/mysql/AphrontMySQLDatabaseConnectionBase.php', 'AphrontMySQLiDatabaseConnection' => 'aphront/storage/connection/mysql/AphrontMySQLiDatabaseConnection.php', 'AphrontQueryAccessDeniedException' => 'aphront/storage/exception/AphrontQueryAccessDeniedException.php', 'AphrontQueryConnectionException' => 'aphront/storage/exception/AphrontQueryConnectionException.php', 'AphrontQueryConnectionLostException' => 'aphront/storage/exception/AphrontQueryConnectionLostException.php', 'AphrontQueryCountException' => 'aphront/storage/exception/AphrontQueryCountException.php', 'AphrontQueryDeadlockException' => 'aphront/storage/exception/AphrontQueryDeadlockException.php', 'AphrontQueryDuplicateKeyException' => 'aphront/storage/exception/AphrontQueryDuplicateKeyException.php', 'AphrontQueryException' => 'aphront/storage/exception/AphrontQueryException.php', 'AphrontQueryNotSupportedException' => 'aphront/storage/exception/AphrontQueryNotSupportedException.php', 'AphrontQueryObjectMissingException' => 'aphront/storage/exception/AphrontQueryObjectMissingException.php', 'AphrontQueryParameterException' => 'aphront/storage/exception/AphrontQueryParameterException.php', 'AphrontQueryRecoverableException' => 'aphront/storage/exception/AphrontQueryRecoverableException.php', 'AphrontQuerySchemaException' => 'aphront/storage/exception/AphrontQuerySchemaException.php', 'AphrontScopedUnguardedWriteCapability' => 'aphront/writeguard/AphrontScopedUnguardedWriteCapability.php', 'AphrontWriteGuard' => 'aphront/writeguard/AphrontWriteGuard.php', 'BaseHTTPFuture' => 'future/http/BaseHTTPFuture.php', 'CommandException' => 'future/exec/CommandException.php', 'ConduitClient' => 'conduit/ConduitClient.php', 'ConduitClientException' => 'conduit/ConduitClientException.php', 'ConduitFuture' => 'conduit/ConduitFuture.php', 'ExecFuture' => 'future/exec/ExecFuture.php', 'ExecFutureTestCase' => 'future/exec/__tests__/ExecFutureTestCase.php', 'FileFinder' => 'filesystem/FileFinder.php', 'FileList' => 'filesystem/FileList.php', 'Filesystem' => 'filesystem/Filesystem.php', 'FilesystemException' => 'filesystem/FilesystemException.php', 'Future' => 'future/Future.php', 'FutureIterator' => 'future/FutureIterator.php', 'FutureIteratorTestCase' => 'future/__tests__/FutureIteratorTestCase.php', 'FutureProxy' => 'future/FutureProxy.php', 'HTTPFuture' => 'future/http/HTTPFuture.php', 'HTTPFutureResponseStatus' => 'future/http/status/HTTPFutureResponseStatus.php', 'HTTPFutureResponseStatusCURL' => 'future/http/status/HTTPFutureResponseStatusCURL.php', 'HTTPFutureResponseStatusHTTP' => 'future/http/status/HTTPFutureResponseStatusHTTP.php', 'HTTPFutureResponseStatusParse' => 'future/http/status/HTTPFutureResponseStatusParse.php', 'HTTPFutureResponseStatusTransport' => 'future/http/status/HTTPFutureResponseStatusTransport.php', 'HTTPSFuture' => 'future/http/HTTPSFuture.php', 'ImmediateFuture' => 'future/ImmediateFuture.php', 'LinesOfALarge' => 'filesystem/linesofalarge/LinesOfALarge.php', 'LinesOfALargeExecFuture' => 'filesystem/linesofalarge/LinesOfALargeExecFuture.php', 'LinesOfALargeExecFutureTestCase' => 'filesystem/linesofalarge/__tests__/LinesOfALargeExecFutureTestCase.php', 'LinesOfALargeFile' => 'filesystem/linesofalarge/LinesOfALargeFile.php', 'LinesOfALargeFileTestCase' => 'filesystem/linesofalarge/__tests__/LinesOfALargeFileTestCase.php', 'MFilterTestHelper' => 'utils/__tests__/MFilterTestHelper.php', 'Phobject' => 'object/Phobject.php', 'PhutilAWSEC2Future' => 'future/aws/PhutilAWSEC2Future.php', 'PhutilAWSException' => 'future/aws/PhutilAWSException.php', 'PhutilAWSFuture' => 'future/aws/PhutilAWSFuture.php', 'PhutilAggregateException' => 'error/PhutilAggregateException.php', 'PhutilArgumentParser' => 'parser/argument/PhutilArgumentParser.php', 'PhutilArgumentParserException' => 'parser/argument/exception/PhutilArgumentParserException.php', 'PhutilArgumentParserTestCase' => 'parser/argument/__tests__/PhutilArgumentParserTestCase.php', 'PhutilArgumentSpecification' => 'parser/argument/PhutilArgumentSpecification.php', 'PhutilArgumentSpecificationException' => 'parser/argument/exception/PhutilArgumentSpecificationException.php', 'PhutilArgumentSpecificationTestCase' => 'parser/argument/__tests__/PhutilArgumentSpecificationTestCase.php', 'PhutilArgumentUsageException' => 'parser/argument/exception/PhutilArgumentUsageException.php', 'PhutilArgumentWorkflow' => 'parser/argument/workflow/PhutilArgumentWorkflow.php', 'PhutilArray' => 'utils/PhutilArray.php', 'PhutilArrayTestCase' => 'utils/__tests__/PhutilArrayTestCase.php', 'PhutilArrayWithDefaultValue' => 'utils/PhutilArrayWithDefaultValue.php', 'PhutilBufferedIterator' => 'utils/PhutilBufferedIterator.php', 'PhutilBufferedIteratorExample' => 'utils/PhutilBufferedIteratorExample.php', 'PhutilBufferedIteratorTestCase' => 'utils/__tests__/PhutilBufferedIteratorTestCase.php', 'PhutilCallbackFilterIterator' => 'utils/PhutilCallbackFilterIterator.php', 'PhutilChannel' => 'channel/PhutilChannel.php', 'PhutilChannelChannel' => 'channel/PhutilChannelChannel.php', 'PhutilChunkedIterator' => 'utils/PhutilChunkedIterator.php', 'PhutilChunkedIteratorTestCase' => 'utils/__tests__/PhutilChunkedIteratorTestCase.php', 'PhutilConsole' => 'console/PhutilConsole.php', 'PhutilConsoleFormatter' => 'console/PhutilConsoleFormatter.php', 'PhutilConsoleMessage' => 'console/PhutilConsoleMessage.php', 'PhutilConsoleServer' => 'console/PhutilConsoleServer.php', 'PhutilConsoleServerChannel' => 'console/PhutilConsoleServerChannel.php', 'PhutilConsoleStdinNotInteractiveException' => 'console/PhutilConsoleStdinNotInteractiveException.php', 'PhutilConsoleSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilConsoleSyntaxHighlighter.php', 'PhutilConsoleWrapTestCase' => 'console/__tests__/PhutilConsoleWrapTestCase.php', 'PhutilDaemon' => 'daemon/PhutilDaemon.php', 'PhutilDaemonOverseer' => 'daemon/PhutilDaemonOverseer.php', 'PhutilDefaultSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilDefaultSyntaxHighlighter.php', 'PhutilDefaultSyntaxHighlighterEngine' => 'markup/syntax/engine/PhutilDefaultSyntaxHighlighterEngine.php', 'PhutilDefaultSyntaxHighlighterEnginePygmentsFuture' => 'markup/syntax/highlighter/pygments/PhutilDefaultSyntaxHighlighterEnginePygmentsFuture.php', 'PhutilDefaultSyntaxHighlighterEngineTestCase' => 'markup/syntax/engine/__tests__/PhutilDefaultSyntaxHighlighterEngineTestCase.php', 'PhutilDeferredLog' => 'filesystem/PhutilDeferredLog.php', 'PhutilDeferredLogTestCase' => 'filesystem/__tests__/PhutilDeferredLogTestCase.php', 'PhutilDirectoryFixture' => 'filesystem/PhutilDirectoryFixture.php', 'PhutilDivinerSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilDivinerSyntaxHighlighter.php', 'PhutilDocblockParser' => 'parser/PhutilDocblockParser.php', 'PhutilDocblockParserTestCase' => 'parser/__tests__/PhutilDocblockParserTestCase.php', 'PhutilEmailAddress' => 'parser/PhutilEmailAddress.php', 'PhutilEmailAddressTestCase' => 'parser/__tests__/PhutilEmailAddressTestCase.php', 'PhutilErrorHandler' => 'error/PhutilErrorHandler.php', 'PhutilErrorHandlerTestCase' => 'error/__tests__/PhutilErrorHandlerTestCase.php', 'PhutilEvent' => 'events/PhutilEvent.php', 'PhutilEventConstants' => 'events/constant/PhutilEventConstants.php', 'PhutilEventEngine' => 'events/PhutilEventEngine.php', 'PhutilEventListener' => 'events/PhutilEventListener.php', 'PhutilEventType' => 'events/constant/PhutilEventType.php', 'PhutilExcessiveServiceCallsDaemon' => 'daemon/torture/PhutilExcessiveServiceCallsDaemon.php', 'PhutilExecChannel' => 'channel/PhutilExecChannel.php', 'PhutilFatalDaemon' => 'daemon/torture/PhutilFatalDaemon.php', 'PhutilFileLock' => 'filesystem/PhutilFileLock.php', 'PhutilFileLockTestCase' => 'filesystem/__tests__/PhutilFileLockTestCase.php', 'PhutilFileTree' => 'filesystem/PhutilFileTree.php', 'PhutilGitURI' => 'parser/PhutilGitURI.php', 'PhutilGitURITestCase' => 'parser/__tests__/PhutilGitURITestCase.php', 'PhutilHangForeverDaemon' => 'daemon/torture/PhutilHangForeverDaemon.php', 'PhutilHelpArgumentWorkflow' => 'parser/argument/workflow/PhutilHelpArgumentWorkflow.php', 'PhutilInteractiveEditor' => 'console/PhutilInteractiveEditor.php', 'PhutilJSON' => 'parser/PhutilJSON.php', 'PhutilJSONProtocolChannel' => 'channel/PhutilJSONProtocolChannel.php', 'PhutilJSONProtocolChannelTestCase' => 'channel/__tests__/PhutilJSONProtocolChannelTestCase.php', 'PhutilJSONTestCase' => 'parser/__tests__/PhutilJSONTestCase.php', 'PhutilKeyValueCache' => 'cache/PhutilKeyValueCache.php', 'PhutilKeyValueCacheAPC' => 'cache/PhutilKeyValueCacheAPC.php', 'PhutilKeyValueCacheInRequest' => 'cache/PhutilKeyValueCacheInRequest.php', 'PhutilKeyValueCacheMemcache' => 'cache/PhutilKeyValueCacheMemcache.php', 'PhutilKeyValueCacheOnDisk' => 'cache/PhutilKeyValueCacheOnDisk.php', 'PhutilKeyValueCacheStack' => 'cache/PhutilKeyValueCacheStack.php', 'PhutilKeyValueCacheTestCase' => 'cache/__tests__/PhutilKeyValueCacheTestCase.php', 'PhutilLanguageGuesser' => 'parser/PhutilLanguageGuesser.php', 'PhutilLanguageGuesserTestCase' => 'parser/__tests__/PhutilLanguageGuesserTestCase.php', 'PhutilLexer' => 'lexer/PhutilLexer.php', 'PhutilLexerSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilLexerSyntaxHighlighter.php', 'PhutilLock' => 'filesystem/PhutilLock.php', 'PhutilLockException' => 'filesystem/PhutilLockException.php', 'PhutilMarkupEngine' => 'markup/PhutilMarkupEngine.php', 'PhutilMarkupTestCase' => 'markup/__tests__/PhutilMarkupTestCase.php', + 'PhutilMetricsChannel' => 'channel/PhutilMetricsChannel.php', 'PhutilMissingSymbolException' => 'symbols/exception/PhutilMissingSymbolException.php', 'PhutilNiceDaemon' => 'daemon/torture/PhutilNiceDaemon.php', 'PhutilOpaqueEnvelope' => 'error/PhutilOpaqueEnvelope.php', 'PhutilOpaqueEnvelopeKey' => 'error/PhutilOpaqueEnvelopeKey.php', 'PhutilOpaqueEnvelopeTestCase' => 'error/__tests__/PhutilOpaqueEnvelopeTestCase.php', 'PhutilPHPFragmentLexer' => 'lexer/PhutilPHPFragmentLexer.php', 'PhutilPHPFragmentLexerHighlighterTestCase' => 'markup/syntax/highlighter/__tests__/PhutilPHPFragmentLexerHighlighterTestCase.php', 'PhutilPHPFragmentLexerTestCase' => 'lexer/__tests__/PhutilPHPFragmentLexerTestCase.php', 'PhutilPHPObjectProtocolChannel' => 'channel/PhutilPHPObjectProtocolChannel.php', 'PhutilPHPObjectProtocolChannelTestCase' => 'channel/__tests__/PhutilPHPObjectProtocolChannelTestCase.php', 'PhutilPHTTestCase' => 'internationalization/__tests__/PhutilPHTTestCase.php', 'PhutilPerson' => 'internationalization/PhutilPerson.php', 'PhutilPersonTest' => 'internationalization/__tests__/PhutilPersonTest.php', 'PhutilProcessGroupDaemon' => 'daemon/torture/PhutilProcessGroupDaemon.php', 'PhutilProtocolChannel' => 'channel/PhutilProtocolChannel.php', 'PhutilProxyException' => 'error/PhutilProxyException.php', 'PhutilPygmentsSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilPygmentsSyntaxHighlighter.php', 'PhutilRainbowSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilRainbowSyntaxHighlighter.php', 'PhutilReadableSerializer' => 'readableserializer/PhutilReadableSerializer.php', 'PhutilReadableSerializerTestCase' => 'readableserializer/__tests__/PhutilReadableSerializerTestCase.php', 'PhutilRemarkupBlockStorage' => 'markup/engine/remarkup/PhutilRemarkupBlockStorage.php', 'PhutilRemarkupEngine' => 'markup/engine/PhutilRemarkupEngine.php', 'PhutilRemarkupEngineBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineBlockRule.php', 'PhutilRemarkupEngineRemarkupCodeBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupCodeBlockRule.php', 'PhutilRemarkupEngineRemarkupDefaultBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupDefaultBlockRule.php', 'PhutilRemarkupEngineRemarkupHeaderBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupHeaderBlockRule.php', 'PhutilRemarkupEngineRemarkupInlineBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupInlineBlockRule.php', 'PhutilRemarkupEngineRemarkupListBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupListBlockRule.php', 'PhutilRemarkupEngineRemarkupLiteralBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupLiteralBlockRule.php', 'PhutilRemarkupEngineRemarkupNoteBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupNoteBlockRule.php', 'PhutilRemarkupEngineRemarkupQuotesBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupQuotesBlockRule.php', 'PhutilRemarkupEngineRemarkupSimpleTableBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupSimpleTableBlockRule.php', 'PhutilRemarkupEngineRemarkupTableBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupTableBlockRule.php', 'PhutilRemarkupEngineTestCase' => 'markup/engine/__tests__/PhutilRemarkupEngineTestCase.php', 'PhutilRemarkupRule' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRule.php', 'PhutilRemarkupRuleBold' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleBold.php', 'PhutilRemarkupRuleDel' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleDel.php', 'PhutilRemarkupRuleDocumentLink' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleDocumentLink.php', 'PhutilRemarkupRuleEscapeHTML' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleEscapeHTML.php', 'PhutilRemarkupRuleEscapeRemarkup' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleEscapeRemarkup.php', 'PhutilRemarkupRuleHyperlink' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleHyperlink.php', 'PhutilRemarkupRuleItalic' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleItalic.php', 'PhutilRemarkupRuleLinebreaks' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleLinebreaks.php', 'PhutilRemarkupRuleMonospace' => 'markup/engine/remarkup/markuprule/PhutilRemarkupRuleMonospace.php', 'PhutilSaturateStdoutDaemon' => 'daemon/torture/PhutilSaturateStdoutDaemon.php', 'PhutilServiceProfiler' => 'serviceprofiler/PhutilServiceProfiler.php', 'PhutilShellLexer' => 'lexer/PhutilShellLexer.php', 'PhutilShellLexerTestCase' => 'lexer/__tests__/PhutilShellLexerTestCase.php', 'PhutilSimpleOptions' => 'parser/PhutilSimpleOptions.php', 'PhutilSimpleOptionsTestCase' => 'parser/__tests__/PhutilSimpleOptionsTestCase.php', 'PhutilSocketChannel' => 'channel/PhutilSocketChannel.php', 'PhutilSprite' => 'sprites/PhutilSprite.php', 'PhutilSpriteSheet' => 'sprites/PhutilSpriteSheet.php', 'PhutilSymbolLoader' => 'symbols/PhutilSymbolLoader.php', 'PhutilSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilSyntaxHighlighter.php', 'PhutilSyntaxHighlighterEngine' => 'markup/syntax/engine/PhutilSyntaxHighlighterEngine.php', 'PhutilSyntaxHighlighterException' => 'markup/syntax/highlighter/PhutilSyntaxHighlighterException.php', 'PhutilTestCase' => 'infrastructure/testing/PhutilTestCase.php', 'PhutilTortureTestDaemon' => 'daemon/torture/PhutilTortureTestDaemon.php', 'PhutilTranslator' => 'internationalization/PhutilTranslator.php', 'PhutilTranslatorTestCase' => 'internationalization/__tests__/PhutilTranslatorTestCase.php', 'PhutilURI' => 'parser/PhutilURI.php', 'PhutilURITestCase' => 'parser/__tests__/PhutilURITestCase.php', 'PhutilUTF8TestCase' => 'utils/__tests__/PhutilUTF8TestCase.php', 'PhutilUtilsTestCase' => 'utils/__tests__/PhutilUtilsTestCase.php', 'PhutilXHPASTSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilXHPASTSyntaxHighlighter.php', 'PhutilXHPASTSyntaxHighlighterTestCase' => 'markup/syntax/highlighter/__tests__/PhutilXHPASTSyntaxHighlighterTestCase.php', 'TempFile' => 'filesystem/TempFile.php', 'TestAbstractDirectedGraph' => 'utils/__tests__/TestAbstractDirectedGraph.php', 'XHPASTNode' => 'parser/xhpast/api/XHPASTNode.php', 'XHPASTSyntaxErrorException' => 'parser/xhpast/api/XHPASTSyntaxErrorException.php', 'XHPASTToken' => 'parser/xhpast/api/XHPASTToken.php', 'XHPASTTree' => 'parser/xhpast/api/XHPASTTree.php', 'XHPASTTreeTestCase' => 'parser/xhpast/api/__tests__/XHPASTTreeTestCase.php', ), 'function' => array( 'Futures' => 'future/functions.php', '_qsprintf_check_scalar_type' => 'xsprintf/qsprintf.php', '_qsprintf_check_type' => 'xsprintf/qsprintf.php', 'array_mergev' => 'utils/utils.php', 'array_select_keys' => 'utils/utils.php', 'assert_instances_of' => 'utils/utils.php', 'coalesce' => 'utils/utils.php', 'csprintf' => 'xsprintf/csprintf.php', 'exec_manual' => 'future/exec/execx.php', 'execx' => 'future/exec/execx.php', 'head' => 'utils/utils.php', 'head_key' => 'utils/utils.php', 'hsprintf' => 'markup/render.php', 'id' => 'utils/utils.php', 'idx' => 'utils/utils.php', 'ifilter' => 'utils/utils.php', 'igroup' => 'utils/utils.php', 'ipull' => 'utils/utils.php', 'isort' => 'utils/utils.php', 'jsprintf' => 'xsprintf/jsprintf.php', 'last' => 'utils/utils.php', 'last_key' => 'utils/utils.php', 'ldap_sprintf' => 'xsprintf/ldapsprintf.php', 'mfilter' => 'utils/utils.php', 'mgroup' => 'utils/utils.php', 'mpull' => 'utils/utils.php', 'msort' => 'utils/utils.php', 'newv' => 'utils/utils.php', 'nonempty' => 'utils/utils.php', 'phlog' => 'error/phlog.php', 'pht' => 'internationalization/pht.php', 'phutil_console_confirm' => 'console/format.php', 'phutil_console_format' => 'console/format.php', 'phutil_console_get_terminal_width' => 'console/format.php', 'phutil_console_prompt' => 'console/format.php', 'phutil_console_require_tty' => 'console/format.php', 'phutil_console_wrap' => 'console/format.php', 'phutil_deprecated' => 'moduleutils/moduleutils.php', 'phutil_error_listener_example' => 'error/phlog.php', 'phutil_escape_html' => 'markup/render.php', 'phutil_escape_uri' => 'markup/render.php', 'phutil_escape_uri_path_component' => 'markup/render.php', 'phutil_get_library_name_for_root' => 'moduleutils/moduleutils.php', 'phutil_get_library_root' => 'moduleutils/moduleutils.php', 'phutil_get_library_root_for_path' => 'moduleutils/moduleutils.php', 'phutil_is_utf8' => 'utils/utf8.php', 'phutil_passthru' => 'future/exec/execx.php', 'phutil_render_tag' => 'markup/render.php', 'phutil_split_lines' => 'utils/utils.php', 'phutil_unescape_uri_path_component' => 'markup/render.php', 'phutil_utf8_console_strlen' => 'utils/utf8.php', 'phutil_utf8_convert' => 'utils/utf8.php', 'phutil_utf8_hard_wrap_html' => 'utils/utf8.php', 'phutil_utf8_shorten' => 'utils/utf8.php', 'phutil_utf8_strlen' => 'utils/utf8.php', 'phutil_utf8ize' => 'utils/utf8.php', 'phutil_utf8v' => 'utils/utf8.php', 'phutil_utf8v_codepoints' => 'utils/utf8.php', 'ppull' => 'utils/utils.php', 'qsprintf' => 'xsprintf/qsprintf.php', 'queryfx' => 'xsprintf/queryfx.php', 'queryfx_all' => 'xsprintf/queryfx.php', 'queryfx_one' => 'xsprintf/queryfx.php', 'vcsprintf' => 'xsprintf/csprintf.php', 'vjsprintf' => 'xsprintf/jsprintf.php', 'vqsprintf' => 'xsprintf/qsprintf.php', 'vqueryfx' => 'xsprintf/queryfx.php', 'vqueryfx_all' => 'xsprintf/queryfx.php', 'xhp_parser_node_constants' => 'parser/xhpast/parser_nodes.php', 'xhpast_get_binary_path' => 'parser/xhpast/bin/xhpast_parse.php', 'xhpast_get_build_instructions' => 'parser/xhpast/bin/xhpast_parse.php', 'xhpast_get_parser_future' => 'parser/xhpast/bin/xhpast_parse.php', 'xhpast_is_available' => 'parser/xhpast/bin/xhpast_parse.php', 'xhpast_parser_token_constants' => 'parser/xhpast/parser_tokens.php', 'xsprintf' => 'xsprintf/xsprintf.php', 'xsprintf_callback_example' => 'xsprintf/xsprintf.php', 'xsprintf_command' => 'xsprintf/csprintf.php', 'xsprintf_javascript' => 'xsprintf/jsprintf.php', 'xsprintf_ldap' => 'xsprintf/ldapsprintf.php', 'xsprintf_query' => 'xsprintf/qsprintf.php', ), 'xmap' => array( 'AASTNodeList' => array( 0 => 'Iterator', 1 => 'Countable', ), 'AbstractDirectedGraphTestCase' => 'PhutilTestCase', 'AphrontIsolatedDatabaseConnection' => 'AphrontDatabaseConnection', 'AphrontMySQLDatabaseConnection' => 'AphrontMySQLDatabaseConnectionBase', 'AphrontMySQLDatabaseConnectionBase' => 'AphrontDatabaseConnection', 'AphrontMySQLiDatabaseConnection' => 'AphrontMySQLDatabaseConnectionBase', 'AphrontQueryAccessDeniedException' => 'AphrontQueryRecoverableException', 'AphrontQueryConnectionException' => 'AphrontQueryException', 'AphrontQueryConnectionLostException' => 'AphrontQueryRecoverableException', 'AphrontQueryCountException' => 'AphrontQueryException', 'AphrontQueryDeadlockException' => 'AphrontQueryRecoverableException', 'AphrontQueryDuplicateKeyException' => 'AphrontQueryException', 'AphrontQueryException' => 'Exception', 'AphrontQueryNotSupportedException' => 'AphrontQueryException', 'AphrontQueryObjectMissingException' => 'AphrontQueryException', 'AphrontQueryParameterException' => 'AphrontQueryException', 'AphrontQueryRecoverableException' => 'AphrontQueryException', 'AphrontQuerySchemaException' => 'AphrontQueryException', 'BaseHTTPFuture' => 'Future', 'CommandException' => 'Exception', 'ConduitClientException' => 'Exception', 'ConduitFuture' => 'FutureProxy', 'ExecFuture' => 'Future', 'ExecFutureTestCase' => 'PhutilTestCase', 'FilesystemException' => 'Exception', 'FutureIterator' => 'Iterator', 'FutureIteratorTestCase' => 'PhutilTestCase', 'FutureProxy' => 'Future', 'HTTPFuture' => 'BaseHTTPFuture', 'HTTPFutureResponseStatus' => 'Exception', 'HTTPFutureResponseStatusCURL' => 'HTTPFutureResponseStatus', 'HTTPFutureResponseStatusHTTP' => 'HTTPFutureResponseStatus', 'HTTPFutureResponseStatusParse' => 'HTTPFutureResponseStatus', 'HTTPFutureResponseStatusTransport' => 'HTTPFutureResponseStatus', 'HTTPSFuture' => 'BaseHTTPFuture', 'ImmediateFuture' => 'Future', 'LinesOfALarge' => 'Iterator', 'LinesOfALargeExecFuture' => 'LinesOfALarge', 'LinesOfALargeExecFutureTestCase' => 'PhutilTestCase', 'LinesOfALargeFile' => 'LinesOfALarge', 'LinesOfALargeFileTestCase' => 'PhutilTestCase', 'PhutilAWSEC2Future' => 'PhutilAWSFuture', 'PhutilAWSException' => 'Exception', 'PhutilAWSFuture' => 'FutureProxy', 'PhutilAggregateException' => 'Exception', 'PhutilArgumentParserException' => 'Exception', 'PhutilArgumentParserTestCase' => 'PhutilTestCase', 'PhutilArgumentSpecificationException' => 'PhutilArgumentParserException', 'PhutilArgumentSpecificationTestCase' => 'PhutilTestCase', 'PhutilArgumentUsageException' => 'PhutilArgumentParserException', 'PhutilArray' => array( 0 => 'Phobject', 1 => 'Countable', 2 => 'ArrayAccess', 3 => 'Iterator', ), 'PhutilArrayTestCase' => 'PhutilTestCase', 'PhutilArrayWithDefaultValue' => 'PhutilArray', 'PhutilBufferedIterator' => 'Iterator', 'PhutilBufferedIteratorExample' => 'PhutilBufferedIterator', 'PhutilBufferedIteratorTestCase' => 'PhutilTestCase', 'PhutilCallbackFilterIterator' => 'FilterIterator', 'PhutilChannelChannel' => 'PhutilChannel', 'PhutilChunkedIterator' => 'Iterator', 'PhutilChunkedIteratorTestCase' => 'PhutilTestCase', 'PhutilConsoleServerChannel' => 'PhutilChannelChannel', 'PhutilConsoleStdinNotInteractiveException' => 'Exception', 'PhutilConsoleWrapTestCase' => 'PhutilTestCase', 'PhutilDefaultSyntaxHighlighterEngine' => 'PhutilSyntaxHighlighterEngine', 'PhutilDefaultSyntaxHighlighterEnginePygmentsFuture' => 'FutureProxy', 'PhutilDefaultSyntaxHighlighterEngineTestCase' => 'PhutilTestCase', 'PhutilDeferredLogTestCase' => 'PhutilTestCase', 'PhutilDocblockParserTestCase' => 'PhutilTestCase', 'PhutilEmailAddressTestCase' => 'PhutilTestCase', 'PhutilErrorHandlerTestCase' => 'PhutilTestCase', 'PhutilEventType' => 'PhutilEventConstants', 'PhutilExcessiveServiceCallsDaemon' => 'PhutilTortureTestDaemon', 'PhutilExecChannel' => 'PhutilChannel', 'PhutilFatalDaemon' => 'PhutilTortureTestDaemon', 'PhutilFileLock' => 'PhutilLock', 'PhutilFileLockTestCase' => 'PhutilTestCase', 'PhutilGitURITestCase' => 'PhutilTestCase', 'PhutilHangForeverDaemon' => 'PhutilTortureTestDaemon', 'PhutilHelpArgumentWorkflow' => 'PhutilArgumentWorkflow', 'PhutilJSONProtocolChannel' => 'PhutilProtocolChannel', 'PhutilJSONProtocolChannelTestCase' => 'PhutilTestCase', 'PhutilJSONTestCase' => 'PhutilTestCase', 'PhutilKeyValueCacheAPC' => 'PhutilKeyValueCache', 'PhutilKeyValueCacheInRequest' => 'PhutilKeyValueCache', 'PhutilKeyValueCacheMemcache' => 'PhutilKeyValueCache', 'PhutilKeyValueCacheOnDisk' => 'PhutilKeyValueCache', 'PhutilKeyValueCacheStack' => 'PhutilKeyValueCache', 'PhutilKeyValueCacheTestCase' => 'ArcanistPhutilTestCase', 'PhutilLanguageGuesserTestCase' => 'PhutilTestCase', 'PhutilLexerSyntaxHighlighter' => 'PhutilSyntaxHighlighter', 'PhutilLockException' => 'Exception', 'PhutilMarkupTestCase' => 'PhutilTestCase', + 'PhutilMetricsChannel' => 'PhutilChannelChannel', 'PhutilMissingSymbolException' => 'Exception', 'PhutilNiceDaemon' => 'PhutilTortureTestDaemon', 'PhutilOpaqueEnvelopeTestCase' => 'PhutilTestCase', 'PhutilPHPFragmentLexer' => 'PhutilLexer', 'PhutilPHPFragmentLexerHighlighterTestCase' => 'PhutilTestCase', 'PhutilPHPFragmentLexerTestCase' => 'PhutilTestCase', 'PhutilPHPObjectProtocolChannel' => 'PhutilProtocolChannel', 'PhutilPHPObjectProtocolChannelTestCase' => 'PhutilTestCase', 'PhutilPHTTestCase' => 'PhutilTestCase', 'PhutilPersonTest' => 'PhutilPerson', 'PhutilProcessGroupDaemon' => 'PhutilTortureTestDaemon', 'PhutilProtocolChannel' => 'PhutilChannelChannel', 'PhutilProxyException' => 'Exception', 'PhutilReadableSerializerTestCase' => 'PhutilTestCase', 'PhutilRemarkupEngine' => 'PhutilMarkupEngine', 'PhutilRemarkupEngineRemarkupCodeBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupDefaultBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupHeaderBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupInlineBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupListBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupLiteralBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupNoteBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupQuotesBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupSimpleTableBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineRemarkupTableBlockRule' => 'PhutilRemarkupEngineBlockRule', 'PhutilRemarkupEngineTestCase' => 'PhutilTestCase', 'PhutilRemarkupRuleBold' => 'PhutilRemarkupRule', 'PhutilRemarkupRuleDel' => 'PhutilRemarkupRule', 'PhutilRemarkupRuleDocumentLink' => 'PhutilRemarkupRule', 'PhutilRemarkupRuleEscapeHTML' => 'PhutilRemarkupRule', 'PhutilRemarkupRuleEscapeRemarkup' => 'PhutilRemarkupRule', 'PhutilRemarkupRuleHyperlink' => 'PhutilRemarkupRule', 'PhutilRemarkupRuleItalic' => 'PhutilRemarkupRule', 'PhutilRemarkupRuleLinebreaks' => 'PhutilRemarkupRule', 'PhutilRemarkupRuleMonospace' => 'PhutilRemarkupRule', 'PhutilSaturateStdoutDaemon' => 'PhutilTortureTestDaemon', 'PhutilShellLexer' => 'PhutilLexer', 'PhutilShellLexerTestCase' => 'PhutilTestCase', 'PhutilSimpleOptionsTestCase' => 'PhutilTestCase', 'PhutilSocketChannel' => 'PhutilChannel', 'PhutilSyntaxHighlighterException' => 'Exception', 'PhutilTestCase' => 'ArcanistPhutilTestCase', 'PhutilTortureTestDaemon' => 'PhutilDaemon', 'PhutilTranslatorTestCase' => 'PhutilTestCase', 'PhutilURITestCase' => 'PhutilTestCase', 'PhutilUTF8TestCase' => 'PhutilTestCase', 'PhutilUtilsTestCase' => 'PhutilTestCase', 'PhutilXHPASTSyntaxHighlighterTestCase' => 'PhutilTestCase', 'TestAbstractDirectedGraph' => 'AbstractDirectedGraph', 'XHPASTNode' => 'AASTNode', 'XHPASTSyntaxErrorException' => 'Exception', 'XHPASTToken' => 'AASTToken', 'XHPASTTree' => 'AASTTree', 'XHPASTTreeTestCase' => 'PhutilTestCase', ), )); diff --git a/src/channel/PhutilChannel.php b/src/channel/PhutilChannel.php index 3d069e2..e017317 100644 --- a/src/channel/PhutilChannel.php +++ b/src/channel/PhutilChannel.php @@ -1,318 +1,342 @@ ibuf; $this->ibuf = ''; return $result; } /** * Write to the channel. A channel defines what data format it accepts, * so this method may take strings, objects, or anything else. * * The default implementation accepts bytes. * * @param wild Data to write to the channel, normally bytes. * @return this * * @task io */ public function write($bytes) { if (!is_scalar($bytes)) { throw new Exception("PhutilChannel->write() may only write strings!"); } $this->obuf .= $bytes; return $this; } /* -( Waiting for Activity )----------------------------------------------- */ /** * Wait (using select()) for activity on any channel. This method blocks until * some channel is ready to be updated. * * It does not provide a way to determine which channels are ready to be * updated. The expectation is that you'll just update every channel. This * might change eventually. * * Available options are: * * - 'read' (list) Additional streams to select for read. * - 'write' (list) Additional streams to select for write. * - 'except' (list) Additional streams to select for except. * - 'timeout' (float) Select timeout, defaults to 1. * * NOTE: Extra streams must be //streams//, not //sockets//, because this * method uses `stream_select()`, not `socket_select()`. * * @param list A list of channels to wait for. * @param dict Options, see above. * @return void * * @task wait */ public static function waitForAny(array $channels, array $options = array()) { assert_instances_of($channels, 'PhutilChannel'); $read = idx($options, 'read', array()); $write = idx($options, 'write', array()); $except = idx($options, 'except', array()); $wait = idx($options, 'timeout', 1); foreach ($channels as $channel) { // If any of the channels have data in read buffers, return immediately. // If we don't, we risk running select() on a bunch of sockets which won't // become readable because the data the application expects is already // in a read buffer. if (!$channel->isReadBufferEmpty()) { return; } $r_sockets = $channel->getReadSockets(); $w_sockets = $channel->getWriteSockets(); // If any channel has no read sockets and no write sockets, assume it // isn't selectable and return immediately (effectively degrading to a // busy wait). if (!$r_sockets && !$w_sockets) { return; } foreach ($r_sockets as $socket) { $read[] = $socket; $except[] = $socket; } foreach ($w_sockets as $socket) { $write[] = $socket; $except[] = $socket; } } if (!$read && !$write && !$except) { return false; } $wait_sec = (int)$wait; $wait_usec = 1000000 * ($wait - $wait_sec); @stream_select($read, $write, $except, $wait_sec, $wait_usec); } /* -( Responding to Activity )--------------------------------------------- */ /** * Updates the channel, filling input buffers and flushing output buffers. * Returns false if the channel has closed. * * @return bool True if the channel is still open. * * @task update */ public function update() { while (true) { $in = $this->readBytes(); if (!strlen($in)) { // Reading is blocked for now. break; } $this->ibuf .= $in; } while (strlen($this->obuf)) { $len = $this->writeBytes($this->obuf); if (!$len) { // Writing is blocked for now. break; } $this->obuf = substr($this->obuf, $len); } return $this->isOpen(); } /* -( Channel Implementation )--------------------------------------------- */ /** * Set a channel name. This is primarily intended to allow you to debug * channel code more easily, by naming channels something meaningful. * * @param string Channel name. * @return this * * @task impl */ public function setName($name) { $this->name = $name; return $this; } /** * Get the channel name, as set by @{method:setName}. * * @return string Name of the channel. * * @task impl */ public function getName() { return coalesce($this->name, get_class($this)); } /** * Test if the channel is open: active, can be read from and written to, etc. * * @return bool True if the channel is open. * * @task impl */ abstract public function isOpen(); + /** + * Test if the channel is open for reading. + * + * @return bool True if the channel is open for reading. + * + * @task impl + */ + public function isOpenForReading() { + return $this->isOpen(); + } + + + /** + * Test if the channel is open for writing. + * + * @return bool True if the channel is open for writing. + * + * @task impl + */ + public function isOpenForWriting() { + return $this->isOpen(); + } + + /** * Read from the channel's underlying I/O. * * @return string Bytes, if available. * * @task impl */ abstract protected function readBytes(); /** * Write to the channel's underlying I/O. * * @param string Bytes to write. * @return int Number of bytes written. * * @task impl */ abstract protected function writeBytes($bytes); /** * Get sockets to select for reading. * * @return list Read sockets. * * @task impl */ protected function getReadSockets() { return array(); } /** * Get sockets to select for writing. * * @return list Write sockets. * * @task impl */ protected function getWriteSockets() { return array(); } /** * Test state of the read buffer. * * @return bool True if the read buffer is empty. * * @task impl */ protected function isReadBufferEmpty() { return (strlen($this->ibuf) == 0); } /** * Test state of the write buffer. * * @return bool True if the write buffer is empty. * * @task impl */ protected function isWriteBufferEmpty() { return (strlen($this->obuf) == 0); } /** * Wait for any buffered writes to complete. This is a blocking call. When * the call returns, the write buffer will be empty. * * @task impl */ public function flush() { while (!$this->isWriteBufferEmpty()) { self::waitForAny(array($this)); if (!$this->update()) { throw new Exception("Channel closed while flushing output!"); } } return $this; } } diff --git a/src/channel/PhutilChannelChannel.php b/src/channel/PhutilChannelChannel.php index 34a4fc4..58395d0 100644 --- a/src/channel/PhutilChannelChannel.php +++ b/src/channel/PhutilChannelChannel.php @@ -1,82 +1,95 @@ channel = $channel; + $this->didConstruct(); + } + + protected function didConstruct() { + // Hook for subclasses. } public function read() { return $this->channel->read(); } public function write($message) { $this->channel->write($message); return $this; } public function update() { return $this->channel->update(); } public function isOpen() { return $this->channel->isOpen(); } + public function isOpenForReading() { + return $this->channel->isOpenForReading(); + } + + public function isOpenForWriting() { + return $this->channel->isOpenForWriting(); + } + protected function readBytes() { $this->throwOnRawByteOperations(); } protected function writeBytes($bytes) { $this->throwOnRawByteOperations(); } protected function getReadSockets() { return $this->channel->getReadSockets(); } protected function getWriteSockets() { return $this->channel->getWriteSockets(); } protected function isReadBufferEmpty() { return $this->channel->isReadBufferEmpty(); } protected function isWriteBufferEmpty() { return $this->channel->isWriteBufferEmpty(); } public function flush() { $this->channel->flush(); return $this; } protected function getUnderlyingChannel() { return $this->channel; } private function throwOnRawByteOperations() { // NOTE: You should only be able to end up here if you subclass this class // and implement your subclass incorrectly, since the byte methods are // protected. throw new Exception( "Do not call readBytes() or writeBytes() directly on a ". "PhutilChannelChannel. Instead, call read() or write()."); } } diff --git a/src/channel/PhutilMetricsChannel.php b/src/channel/PhutilMetricsChannel.php new file mode 100644 index 0000000..ba55e74 --- /dev/null +++ b/src/channel/PhutilMetricsChannel.php @@ -0,0 +1,87 @@ +bytesWritten; + } + + /** + * Get the number of bytes that have been read from the channel. This excludes + * any bytes which have been received but not actually read by anything, and + * thus may underreport compared to actual activity on the wire. + * + * @return int Bytes read. + * @task metrics + */ + public function getBytesRead() { + return $this->bytesRead; + } + + + /** + * Get the elapsed wall time since this channel opened. + * + * @return float Wall time, in seconds. + * @task metrics + */ + public function getWallTime() { + return microtime(true) - $this->startTime; + } + + +/* -( Implementation )----------------------------------------------------- */ + + + /** + * @task impl + */ + protected function didConstruct() { + $this->startTime = microtime(true); + } + + + /** + * @task impl + */ + public function read() { + $buffer = parent::read(); + $this->bytesRead += strlen($buffer); + return $buffer; + } + + + /** + * @task impl + */ + public function write($message) { + $this->bytesWritten += strlen($message); + return parent::write($message); + } + +} diff --git a/src/channel/PhutilSocketChannel.php b/src/channel/PhutilSocketChannel.php index c64e6a0..ed9e317 100644 --- a/src/channel/PhutilSocketChannel.php +++ b/src/channel/PhutilSocketChannel.php @@ -1,153 +1,191 @@ readSocket = $read_socket; - $this->writeSocket = $write_socket; + if ($write_socket) { + $this->writeSocket = $write_socket; + } else { + $this->writeSocket = $read_socket; + $this->isSingleSocket = true; + } } public function __destruct() { $this->closeSockets(); } /** * Creates a pair of socket channels that are connected to each other. This * is mostly useful for writing unit tests of, e.g., protocol channels. * * list($x, $y) = PhutilSocketChannel::newChannelPair(); * * @task construct */ public static function newChannelPair() { $sockets = null; $domain = phutil_is_windows() ? STREAM_PF_INET : STREAM_PF_UNIX; $pair = stream_socket_pair($domain, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); if (!$pair) { throw new Exception("stream_socket_pair() failed!"); } $x = new PhutilSocketChannel($pair[0]); $y = new PhutilSocketChannel($pair[1]); return array($x, $y); } public function isOpen() { + return ($this->isOpenForReading() || $this->isOpenForWriting()); + } + + public function isOpenForReading() { return (bool)$this->readSocket; } + public function isOpenForWriting() { + return (bool)$this->writeSocket; + } + protected function readBytes() { - $data = @fread($this->readSocket, 4096); + $socket = $this->readSocket; + if (!$socket) { + return ''; + } + + $data = @fread($socket, 4096); if ($data === false) { - $this->closeSockets(); + $this->closeReadSocket(); $data = ''; } // NOTE: fread() continues returning empty string after the socket is // closed, we need to check for EOF explicitly. if ($data === '') { - if (feof($this->readSocket)) { - $this->closeSockets(); + if (feof($socket)) { + $this->closeReadSocket(); } } return $data; } protected function writeBytes($bytes) { $socket = $this->writeSocket; if (!$socket) { - $socket = $this->readSocket; + return 0; } $len = @fwrite($socket, $bytes); if ($len === false) { - $this->closeSockets(); + $this->closeWriteSocket(); return 0; } return $len; } protected function getReadSockets() { - return array($this->readSocket); + if ($this->readSocket) { + return array($this->readSocket); + } + return array(); } protected function getWriteSockets() { if ($this->isWriteBufferEmpty()) { return array(); } else if ($this->writeSocket) { return array($this->writeSocket); } else { - return array($this->readSocket); + return array(); } } - private function closeSockets() { - foreach (array($this->readSocket, $this->writeSocket) as $socket) { - if (!$socket) { - continue; - } - - // We should also stream_socket_shutdown() here but HHVM throws errors - // with it (for example 'Unexpected object type PlainFile'). We depend - // just on fclose() until it is fixed. - @fclose($socket); - } + private function closeReadSocket() { + $this->closeOneSocket($this->readSocket); $this->readSocket = null; + if ($this->isSingleSocket) { + $this->writeSocket = null; + } + } + + private function closeWriteSocket() { + $this->closeOneSocket($this->writeSocket); $this->writeSocket = null; + if ($this->isSingleSocket) { + $this->readSocket = null; + } + } + + private function closeOneSocket($socket) { + if (!$socket) { + return; + } + // We should also stream_socket_shutdown() here but HHVM throws errors + // with it (for example 'Unexpected object type PlainFile'). We depend + // just on fclose() until it is fixed. + @fclose($socket); + } + + private function closeSockets() { + $this->closeReadSocket(); + $this->closeWriteSocket(); } }