├── .dockerignore ├── .gitignore ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── README_zh_CN.md ├── composer.json ├── doc ├── DocList.md ├── Docker.md ├── images │ ├── QQChat.png │ ├── koala-midi.png │ ├── koala-midi_en-US.png │ ├── weixin_add_friend.jpg │ ├── wiki-fpm-1.png │ ├── wiki-fpm-2.png │ ├── wiki-fpm-3.png │ ├── wiki-record-point.png │ ├── wiki-record.png │ ├── wiki-replay.png │ └── wiki-request-split.png ├── midi │ ├── Config.md │ ├── Config_en-US.md │ ├── Elastic-Search.md │ ├── Elastic-Search_en-US.md │ ├── Plugin.md │ ├── Plugin_en-US.md │ └── Replay-file.md └── recorder │ └── recorder.md ├── example └── php │ ├── 1548160113499755925-1158745 │ ├── README.md │ ├── apcu.php │ ├── docker │ ├── start.sh │ └── supervisor.conf │ ├── index.php │ ├── nginx.conf │ ├── phpunit.xml.dist │ ├── report-coverage.png │ ├── report-trace.png │ ├── report-upstream.png │ ├── report.png │ ├── xdebug-at-breakpoint.png │ ├── xdebug-breakpoint.png │ └── xdebug-mapping.png ├── koala-libc ├── .gitignore ├── LICENSE ├── README.md ├── allocated_string.h ├── build.sh ├── countlog.h ├── hook.c └── span.h ├── koala ├── LICENSE ├── README.md ├── build.sh ├── ch │ └── ch.go ├── cmd │ ├── recorder │ │ └── main.go │ └── replayer │ │ └── main.go ├── envarg │ ├── envarg.go │ ├── setup.go │ ├── tag_with_recorder.go │ ├── tag_with_replayer.go │ ├── tag_without_recorder.go │ └── tag_without_replayer.go ├── gateway │ ├── README.md │ ├── gw4go │ │ └── gateway.go │ └── gw4libc │ │ ├── allocated_string.h │ │ ├── countlog.h │ │ ├── file_hook.cpp │ │ ├── init.c │ │ ├── init.h │ │ ├── interpose.h │ │ ├── main.go │ │ ├── network_hook.cpp │ │ ├── path_hook.cpp │ │ ├── span.h │ │ ├── struct_ch_span.go │ │ ├── struct_sockaddr_in.go │ │ ├── tag_without_darwin.go │ │ ├── thread_id.c │ │ ├── thread_id.h │ │ ├── time_hook.cpp │ │ ├── time_hook.h │ │ └── version.c ├── glide.yaml ├── inbound │ └── inboud.go ├── internal │ ├── tag_with_koala_go.go │ └── tag_with_original_go.go ├── koala.go ├── outbound │ ├── http.go │ ├── mysql.go │ └── outbound.go ├── recording │ ├── action.go │ ├── action_test.go │ ├── async.go │ ├── id.go │ ├── recorder.go │ ├── session.go │ ├── talk.go │ ├── trace_header.go │ └── trace_header_test.go ├── replaying │ ├── README.md │ ├── action.go │ ├── lexer │ │ ├── lexer.go │ │ ├── lexer_http.go │ │ ├── lexer_http_test.go │ │ └── sapi.go │ ├── matcher.go │ ├── matcher_chunk.go │ ├── matcher_cosine.go │ ├── matcher_cosine_test.go │ ├── replayed.go │ ├── replaying.go │ ├── replaying_match.go │ ├── replaying_test.go │ └── tmp.go ├── sut │ ├── helper.go │ ├── mock_file.go │ ├── socket.go │ ├── socket_fd.go │ ├── socket_test.go │ ├── state.go │ ├── thread.go │ ├── thread_test.go │ └── time_hook.go └── test │ ├── .gitignore │ ├── java │ ├── .gitignore │ └── Server.java │ ├── server.py │ ├── server │ └── main.go │ └── test.py ├── output ├── bin │ ├── midi-diplugin.phar │ └── midi.phar └── libs │ ├── koala-libc.so │ ├── koala-recorder.so │ └── koala-replayer.so └── php └── midi ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bin └── midi ├── box-diplugin.json ├── box.json ├── build.sh ├── doc ├── DiPlugin │ ├── midi-didi-diff.png │ ├── midi-didi-log.png │ └── midi-didi-upstream.png └── xdebug │ ├── Breakpoint.png │ ├── DBGp.png │ ├── Debug.png │ ├── Listen.png │ ├── Servers-Index.png │ ├── Servers-nuwa-mapping.png │ ├── Servers.png │ ├── Xdebug-Show.png │ ├── Xdebug.png │ └── prework.png ├── install.sh ├── phpunit.xml.dist ├── res ├── diplugin │ ├── README.md │ ├── coverage │ │ └── example.xml.dist │ └── disf │ │ ├── naming.json │ │ └── services.json ├── patches │ └── php-code-coverage.patch ├── replayer │ └── koala-replayer.so └── template │ └── report │ ├── index.twig │ ├── replayed-alter.twig │ ├── replayed-session-list.twig │ ├── replayed-tab-coverage.twig │ ├── replayed-tab-log.twig │ ├── replayed-tab-request.twig │ ├── replayed-tab-trace.twig │ ├── replayed-tab-upstream.twig │ ├── replayed-trace.twig │ └── static │ ├── bootstrap │ └── 4.1.3 │ │ ├── css │ │ ├── bootstrap-grid.css │ │ ├── bootstrap-grid.css.map │ │ ├── bootstrap-grid.min.css │ │ ├── bootstrap-grid.min.css.map │ │ ├── bootstrap-reboot.css │ │ ├── bootstrap-reboot.css.map │ │ ├── bootstrap-reboot.min.css │ │ ├── bootstrap-reboot.min.css.map │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ ├── bootstrap.min.css │ │ └── bootstrap.min.css.map │ │ └── js │ │ ├── bootstrap.bundle.js │ │ ├── bootstrap.bundle.js.map │ │ ├── bootstrap.bundle.min.js │ │ ├── bootstrap.bundle.min.js.map │ │ ├── bootstrap.js │ │ ├── bootstrap.js.map │ │ ├── bootstrap.min.js │ │ └── bootstrap.min.js.map │ ├── collapsible │ ├── CollapsibleLists.js │ ├── button-closed.png │ ├── button-open.png │ ├── button.png │ ├── list-item-contents.png │ ├── list-item-last-open.png │ ├── list-item-last.png │ ├── list-item-open.png │ ├── list-item-root.png │ └── list-item.png │ ├── diff_match_patch.js │ ├── jquery-3.3.1.slim.min.js │ ├── popper-1.14.3.min.js │ ├── report.css │ └── report.js ├── src ├── DiPlugin │ ├── Command │ │ ├── DoctorCommand.php │ │ ├── InitCommand.php │ │ └── SearchCommand.php │ ├── DiConfig.php │ ├── ElasticPlugin.php │ ├── Inject.php │ ├── Message.php │ ├── Mock │ │ ├── FixCI.php │ │ ├── MockDir.php │ │ ├── MockDisf.php │ │ └── MockFile.php │ ├── Parser │ │ └── ParseApollo.php │ ├── Plugin.php │ ├── README.md │ ├── Reporter │ │ ├── Reporter.php │ │ └── Template │ │ │ └── replayed-tab-apollo.twig │ ├── Resolver │ │ ├── ElasticResolver.php │ │ └── EsDSL.php │ ├── Uploader │ │ ├── BaseMate.php │ │ ├── ReplayMate.php │ │ ├── SearchMate.php │ │ └── Uploader.php │ └── Util │ │ ├── Helper.php │ │ └── PreKoalaCheck.php ├── Midi │ ├── Command │ │ ├── BaseCommand.php │ │ ├── ReplayerCommand.php │ │ ├── RunCommand.php │ │ └── UpdateCommand.php │ ├── Config.php │ ├── Config.yml │ ├── Console │ │ └── Application.php │ ├── Container.php │ ├── Differ │ │ ├── Differ.php │ │ └── DifferInterface.php │ ├── ElasticPlugin.php │ ├── EventDispatcher │ │ ├── Event.php │ │ ├── EventDispatcher.php │ │ └── EventSubscriberInterface.php │ ├── Exception │ │ ├── ContainerException.php │ │ ├── ContainerValueNotFoundException.php │ │ ├── Exception.php │ │ ├── InvalidArgumentException.php │ │ ├── KoalaNotStartException.php │ │ ├── KoalaRespEmptyException.php │ │ ├── KoalaResponseException.php │ │ ├── ResolveInvalidParam.php │ │ └── RuntimeException.php │ ├── Factory.php │ ├── Koala │ │ ├── Common │ │ │ ├── AbstractEnum.php │ │ │ └── TypeConverter.php │ │ ├── Koala.php │ │ ├── Lexer.php │ │ ├── Matcher.php │ │ ├── ParseRecorded.php │ │ ├── README.md │ │ ├── Replayed │ │ │ ├── AppendFile.php │ │ │ ├── CallFromInbound.php │ │ │ ├── CallFunction.php │ │ │ ├── CallFunctionArg.php │ │ │ ├── CallOutbound.php │ │ │ ├── Peer.php │ │ │ ├── ReplayedAction.php │ │ │ ├── ReplayedSession.php │ │ │ ├── ReturnFunction.php │ │ │ ├── ReturnInbound.php │ │ │ ├── ReturnValue.php │ │ │ └── SendUDP.php │ │ └── Replaying │ │ │ ├── CallFromInbound.php │ │ │ ├── CallOutbound.php │ │ │ ├── Peer.php │ │ │ ├── ReplayingSession.php │ │ │ └── ReturnInbound.php │ ├── Message.php │ ├── Midi.php │ ├── Mock │ │ ├── MockDir.php │ │ └── MockStorage.php │ ├── Parser │ │ ├── ParseFCGI.php │ │ ├── ParseHTTP.php │ │ ├── ParseInterface.php │ │ ├── ParseMySQL.php │ │ ├── ParseRedis.php │ │ ├── ParseReplayed.php │ │ ├── ParseReplayedInterface.php │ │ ├── ParseSendSync.php │ │ └── ParseThrift.php │ ├── Plugin │ │ ├── Event │ │ │ ├── CommandConfigureEvent.php │ │ │ ├── CommandEvent.php │ │ │ ├── PostCommandEvent.php │ │ │ ├── PostParseSessionEvent.php │ │ │ ├── PostReplaySessionEvent.php │ │ │ ├── PreCommandEvent.php │ │ │ ├── PreKoalaStart.php │ │ │ ├── PreReplaySessionEvent.php │ │ │ └── SessionsSolvingEvent.php │ │ ├── PluginEvents.php │ │ ├── PluginInterface.php │ │ └── PreloadPluginInterface.php │ ├── Reporter │ │ ├── Coverage.php │ │ ├── Coverage │ │ │ ├── Configuration.php │ │ │ └── Xml.php │ │ ├── ReportInterface.php │ │ ├── Reporter.php │ │ └── Tracer.php │ ├── Resolver │ │ ├── ElasticResolver.php │ │ ├── EsDSL.php │ │ ├── FileResolver.php │ │ └── ResolverInterface.php │ └── Util │ │ ├── BM.php │ │ ├── FileUtil.php │ │ ├── OS.php │ │ └── Util.php └── bootstrap.php ├── tests ├── Midi │ └── Test │ │ ├── ConfigTest.php │ │ ├── ContainerTest.php │ │ ├── Koala │ │ └── MatcherTest.php │ │ └── Parser │ │ ├── ParseHTTPTest.php │ │ └── ParseMySQLTest.php └── bootstrap.php ├── version-dev.txt └── version.txt /.dockerignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | doc/ 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /koala/vendor 2 | /php/midi/vendor 3 | /php/midi/composer.json 4 | /php/midi/composer.lock 5 | /koala/.idea 6 | /koala/glide.lock 7 | /koala-libc/.idea 8 | .idea 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | Thanks for considering to contribute this project. All issues and pull requests are highly appreciated. 4 | 5 | ## Report Issue 6 | 7 | Reporting an [issue](https://github.com/didi/rdebug/issues) is welcomed. But do please include the following details 8 | 9 | 1. Environment (OS version, rdebug version and so on) 10 | 2. Issue description, error logs 11 | 3. Steps to reproduce 12 | 13 | ## Pull Requests 14 | 15 | Before sending pull request to this project, please read and follow guidelines below. 16 | 17 | 1. Coding style: PHP follow PSR-2 coding style. 18 | 3. Commit message: Use English and be aware of your spell. 19 | 4. Test: Make sure to test your code. 20 | 21 | NOTE: We assume all your contribution can be licensed under the [Apache License 2.0](./LICENSE). 22 | 23 | ## Run Tests 24 | 25 | To run tests simply run the `phpunit` executable in the `vendor/bin` 26 | 27 | ```bash 28 | $ composer install --dev 29 | $ ./vendor/bin/phpunit 30 | ``` 31 | 32 | You should get an output similar to this: 33 | 34 | ```bash 35 | $ ./vendor/bin/phpunit 36 | PHPUnit 6.5.14 by Sebastian Bergmann and contributors. 37 | 38 | .................SS 19 / 19 (100%) 39 | 40 | Time: 152 ms, Memory: 6.00MB 41 | 42 | OK, but incomplete, skipped, or risky tests! 43 | Tests: 19, Assertions: 28, Skipped: 2. 44 | ``` 45 | 46 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG gopath_default=/tmp/build-golang 2 | 3 | FROM bitnami/minideb-extras:jessie-buildpack as BUILD 4 | 5 | ARG gopath_default 6 | ENV GOPATH=$gopath_default 7 | ENV PATH=$GOPATH/bin:/opt/bitnami/go/bin:$PATH 8 | WORKDIR $GOPATH/src/github.com/didi/rdebug 9 | COPY . $GOPATH/src/github.com/didi/rdebug 10 | 11 | RUN mkdir -p $GOPATH/bin && bitnami-pkg install go-1.8.3-0 --checksum 557d43c4099bd852c702094b6789293aed678b253b80c34c764010a9449ff136 12 | RUN curl https://glide.sh/get | sh && bitnami-pkg install nginx-1.14.0-0 13 | RUN cd koala-libc && sh build.sh \ 14 | && cd ../koala && sh build.sh vendor && sh build.sh && sh build.sh recorder 15 | 16 | FROM bitnami/php-fpm:7.1-debian-8 as FPM 17 | 18 | ARG gopath_default 19 | ENV PATH=/opt/bitnami/nginx/sbin:/opt/bitnami/php/bin:/opt/bitnami/php/sbin:$PATH 20 | WORKDIR /usr/local/var/koala 21 | COPY ./php/midi /usr/local/var/midi 22 | COPY --from=BUILD /opt/bitnami/nginx/sbin /opt/bitnami/nginx/sbin 23 | COPY --from=BUILD /bitnami/nginx/conf /opt/bitnami/nginx/conf 24 | COPY --from=BUILD $gopath_default/src/github.com/didi/rdebug/output/libs/*.so /usr/local/var/koala/ 25 | COPY --from=BUILD $gopath_default/src/github.com/didi/rdebug/output/libs/koala-replayer.so /usr/local/var/midi/res/replayer/ 26 | COPY ./composer.json /usr/local/var/midi/composer.json 27 | COPY ./example/php/nginx.conf /opt/bitnami/nginx/conf 28 | COPY ./example/php/index.php /usr/local/var/koala/index.php 29 | COPY ./example/php/1548160113499755925-1158745 /usr/local/var/koala/1548160113499755925-1158745 30 | COPY ./example/php/docker/start.sh /usr/local/var/koala/start.sh 31 | COPY ./example/php/docker/supervisor.conf /usr/local/var/koala/supervisor.conf 32 | 33 | RUN install_packages apt-utils git vim curl lsof procps ca-certificates sudo locales supervisor && \ 34 | chmod 444 /usr/local/var/koala/*so && \ 35 | addgroup nobody && \ 36 | sed -i -e 's/\s*Defaults\s*secure_path\s*=/# Defaults secure_path=/' /etc/sudoers && \ 37 | echo "nobody ALL=NOPASSWD: ALL" >> /etc/sudoers && \ 38 | sed -i \ 39 | -e "s/pm = ondemand/pm = static/g" \ 40 | -e "s/^listen = 9000/listen = \/usr\/local\/var\/run\/php-fpm.sock/g" \ 41 | -e "s/^;clear_env = no$/clear_env = no/" \ 42 | /opt/bitnami/php/etc/php-fpm.d/www.conf && \ 43 | sed -i \ 44 | -e "s/user=daemon/user=nobody/g" \ 45 | -e "s/^group=daemon/group=nobody/g" \ 46 | -e "s/listen.owner=daemon/listen.owner=nobody/g" \ 47 | -e "s/listen.group=daemon/listen.group=nobody/g" \ 48 | /opt/bitnami/php/etc/common.conf 49 | 50 | EXPOSE 9111 51 | 52 | CMD ["./start.sh"] 53 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rdebug/midi", 3 | "description": "A PHP Replay Client For Rdebug", 4 | "keywords": ["php", "rdebug", "koala", "replay", "Real Debugger"], 5 | "type": "library", 6 | "version": "0.0.6", 7 | "license": "Apache-2.0", 8 | "config": { 9 | "secure-http": false, 10 | "platform": { 11 | "php": "7.0.6" 12 | } 13 | }, 14 | "autoload": { 15 | "psr-4": { 16 | "Midi\\": "php/midi/src/Midi", 17 | "DiPlugin\\": "php/midi/src/DiPlugin" 18 | } 19 | }, 20 | "autoload-dev": { 21 | "psr-4": { 22 | "Midi\\Test\\": "php/midi/tests/Midi/Test" 23 | } 24 | }, 25 | "require": { 26 | "ext-json": "*", 27 | "ext-dom": "*", 28 | "symfony/process": "^3.3", 29 | "symfony/finder": "^3.3", 30 | "symfony/console": "^3.3", 31 | "symfony/stopwatch": "^3.3", 32 | "symfony/event-dispatcher": "^3.3", 33 | "lisachenko/protocol-fcgi": "^1.1", 34 | "pimple/pimple": "^3.2", 35 | "guzzlehttp/guzzle": "^6.3", 36 | "clue/redis-protocol": "^0.3.1", 37 | "twig/twig": "^2.0", 38 | "symfony/yaml": "^3.3", 39 | "apache/thrift": "^0.12.0", 40 | "phpunit/php-code-coverage": "5.3.2", 41 | "vaimo/composer-patches": "3.23.1" 42 | }, 43 | "extra": { 44 | "patches": { 45 | "phpunit/php-code-coverage": [ 46 | { 47 | "label": "php code coverage patch", 48 | "source": "php/midi/res/patches/php-code-coverage.patch", 49 | "version": "^5.3.2", 50 | "level": "1" 51 | } 52 | ] 53 | } 54 | }, 55 | "bin": ["php/midi/bin/midi"], 56 | "require-dev": { 57 | "phpunit/phpunit": "^6.5" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /doc/DocList.md: -------------------------------------------------------------------------------- 1 | # Document list 2 | 3 | - Summary 4 | - [Koala](../koala/README.md) 5 | - [Koala-libc](../koala-libc/README.md) 6 | - [Midi](../php/midi/README.md) 7 | 8 | - DEMO 9 | - [PHP Record & Replay Demo](./example/php/README.md) 10 | - [Replay File Demo](./midi/Replay-file.md) 11 | 12 | - Record 13 | 14 | - [Record](./recorder/recorder.md) 15 | 16 | - Midi 17 | - [Midi Config](./midi/Config.md) / [Midi Config en-US](./midi/Config_en-US.md) 18 | - [Midi Plugin](./midi/Plugin.md) / [Midi Plugin en-US](./midi/Plugin_en-US.md) 19 | -------------------------------------------------------------------------------- /doc/Docker.md: -------------------------------------------------------------------------------- 1 | # Try Rdebug With Docker 2 | 3 | ## Build 4 | 5 | ``` 6 | $ git clone https://github.com/didi/rdebug.git 7 | $ cd rdebug 8 | $ docker build -t local-rdebug-docker . 9 | ``` 10 | 11 | ## Running 12 | 13 | Just Record: 14 | 15 | ``` 16 | $ docker run -v /tmp/recorded:/tmp/save-recorded-sessions -p 9111:9111 --rm local-rdebug-docker 17 | 18 | # New Tab 19 | $ curl 127.0.0.1:9111/index.php 20 | $ curl 127.0.0.1:9111/index.php 21 | $ ls /tmp/recorded 22 | ``` 23 | 24 | Or, Record and Replay: 25 | 26 | ``` 27 | # Enter docker by bash and start nginx & php-fpm 28 | $ docker run -v /tmp/recorded:/tmp/save-recorded-sessions -it --rm local-rdebug-docker bash 29 | > nohup sh start.sh & 30 | 31 | # Record Session 32 | > curl 127.0.0.1:9111/index.php 33 | > curl 127.0.0.1:9111/index.php 34 | 35 | # List recorded session files 36 | > ls /tmp/save-recorded-sessions 37 | 38 | # Install midi's composer dependency 39 | > pushd /usr/local/var/midi 40 | > bash install.sh 41 | > popd 42 | 43 | # Replay Session 44 | > /usr/local/var/midi/bin/midi run -f /usr/local/var/koala/1548160113499755925-1158745 45 | # Or, your recorded session 46 | > /usr/local/var/midi/bin/midi run -f /tmp/save-recorded-sessions/YOUR_SESSION_FILE 47 | ``` -------------------------------------------------------------------------------- /doc/images/QQChat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/doc/images/QQChat.png -------------------------------------------------------------------------------- /doc/images/koala-midi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/doc/images/koala-midi.png -------------------------------------------------------------------------------- /doc/images/koala-midi_en-US.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/doc/images/koala-midi_en-US.png -------------------------------------------------------------------------------- /doc/images/weixin_add_friend.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/doc/images/weixin_add_friend.jpg -------------------------------------------------------------------------------- /doc/images/wiki-fpm-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/doc/images/wiki-fpm-1.png -------------------------------------------------------------------------------- /doc/images/wiki-fpm-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/doc/images/wiki-fpm-2.png -------------------------------------------------------------------------------- /doc/images/wiki-fpm-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/doc/images/wiki-fpm-3.png -------------------------------------------------------------------------------- /doc/images/wiki-record-point.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/doc/images/wiki-record-point.png -------------------------------------------------------------------------------- /doc/images/wiki-record.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/doc/images/wiki-record.png -------------------------------------------------------------------------------- /doc/images/wiki-replay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/doc/images/wiki-replay.png -------------------------------------------------------------------------------- /doc/images/wiki-request-split.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/doc/images/wiki-request-split.png -------------------------------------------------------------------------------- /doc/midi/Elastic-Search.md: -------------------------------------------------------------------------------- 1 | ## ElasticSearch 2 | 3 | ### 创建index 4 | 5 | 流量写入ES前,需要为`SessionId`字段设置以下数据类型,由于ES默认会将该字段设置为`text`,导致在`term`查找的时候,因为`-`导致分词 6 | ``` 7 | PUT /rdebug_index 8 | { 9 | "mappings": { 10 | "_doc": { 11 | "properties": { 12 | "NextSessionId": { 13 | "type": "keyword" 14 | }, 15 | "SessionId": { 16 | "type": "keyword" 17 | } 18 | } 19 | } 20 | } 21 | } 22 | ``` 23 | 24 | ### 使用别名 25 | 26 | 建议通过别名使用ES 27 | ``` 28 | POST _aliases 29 | { 30 | "actions": [ 31 | { 32 | "add": { 33 | "index": "rdebug_index", 34 | "alias": "alias_rdebug_index" 35 | } 36 | } 37 | ] 38 | } 39 | ``` 40 | 41 | ### 配置midi 42 | 43 | 在执行 `midi` 命令的目录下,新建或修改 `.midi/Config.yml` 44 | 45 | 增加扩展命令和ES地址 46 | ``` 47 | php: 48 | 49 | # for elastic 50 | preload-plugins: 51 | - Midi\ElasticPlugin 52 | session-resolver: Midi\Resolver\ElasticResolver 53 | 54 | # set your elastic search url 55 | # http://username:password@ip:port/Index/Type/_search 56 | elastic-search-url: http://xxx.com/alias_rdebug_index/_doc/_search 57 | custom-commands: 58 | - DiPlugin\Command\SearchCommand 59 | ``` -------------------------------------------------------------------------------- /doc/midi/Elastic-Search_en-US.md: -------------------------------------------------------------------------------- 1 | ## ElasticSearch 2 | 3 | ### create index 4 | 5 | Before the traffic is written to the ES,Need to set the data type for the `SessionId` field. Since the ES will set the field to `text` by default, When using `term` to search it, Will be caused analyze by `-` 6 | ``` 7 | PUT /rdebug_index 8 | { 9 | "mappings": { 10 | "_doc": { 11 | "properties": { 12 | "NextSessionId": { 13 | "type": "keyword" 14 | }, 15 | "SessionId": { 16 | "type": "keyword" 17 | } 18 | } 19 | } 20 | } 21 | } 22 | ``` 23 | 24 | ### use alias 25 | 26 | It is recommended to use ES index by alias 27 | ``` 28 | POST _aliases 29 | { 30 | "actions": [ 31 | { 32 | "add": { 33 | "index": "rdebug_index", 34 | "alias": "alias_rdebug_index" 35 | } 36 | } 37 | ] 38 | } 39 | ``` 40 | 41 | ### set midi config 42 | 43 | In the directory where the `midi` command is executed,create or edit `.midi/Config.yml` 44 | 45 | Add an extension command and set ES search URL 46 | ``` 47 | php: 48 | 49 | # for elastic 50 | preload-plugins: 51 | - Midi\ElasticPlugin 52 | session-resolver: Midi\Resolver\ElasticResolver 53 | 54 | # set your elastic search url 55 | # http://username:password@ip:port/Index/Type/_search 56 | elastic-search-url: http://xxx.com/alias_rdebug_index/_doc/_search 57 | custom-commands: 58 | - DiPlugin\Command\SearchCommand 59 | ``` -------------------------------------------------------------------------------- /doc/midi/Plugin.md: -------------------------------------------------------------------------------- 1 | ## Plugin Midi 2 | 3 | Midi 支持很多种方式扩展 midi 的行为。 4 | 5 | 首先,支持两个自定义行为: 6 | 7 | - resolver 查找器 8 | 9 | 查找器定义如何查找录制的流量,譬如命令的 -f 选项,提供对本地文件查找的支持。 10 | 11 | 即 -f /path/to/xxx-session,即找到本地 /path/to/xxx-session 文件作为录制的流量进行回放。 12 | 13 | DiPlugin 提供一个 ElasticPlugin 实现,支持 -i,-o 选项对 elastic 进行搜索录制的流量。 14 | 15 | - differ 对比 16 | 17 | 对回放的结果和录制的数据进行对比,默认的实现只是对比响应的返回值。 18 | 19 | - reporter 报告 20 | 21 | 自定义生成报告内容。 22 | 23 | 这三个自定义行为只需要实现 `Midi\Differ\DifferInterface`、`Midi\Resolver\ResolverInterface` 和 `Midi\Reporter\ReporterInterface` 接口。 24 | 25 | 在配置里,把类名设置到 `session-resolver`、`differ` 和 `reporter` 配置项即可。 26 | 27 | 如果自定义行为的代码 和 phar 没有一起打包,需要在配置里设置下自动加载规则,使 phar 包能找到。 28 | 29 | 其次,Midi 提供插件和事件机制。支持两种类型插件,对应的配置项是 `preload-plugins` and `plugins`。 30 | 31 | 两种插件本质上,没有区别,只是加载时期不同。 32 | 33 | ### `preload-plugins` 34 | 35 | 在 console application 对象实例化之前,将加载此插件。 36 | 37 | 通过实现 `\Midi\Plugin\PreloadPluginInterface` 接口,即可实现此类插件。 38 | 39 | 此类插件,最主要的作用是订阅 `COMMAND_CONFIGURE` 事件,从而来自定义命令的选项。 40 | 41 | ### `plugins` 42 | 43 | 在 application 实例化后,执行 doRun 的时候,将加载此插件。 44 | 45 | 通过实现 `\Midi\Plugin\PluginInterface` 接口,即可实现此类插件。 46 | 47 | ### `Plugin events` 48 | 49 | #### 事件接口 50 | 51 | Midi 内部埋点大量的事件,插件可以实现 `\Midi\EventDispatcher\EventSubscriberInterface` 来订阅感兴趣的事件。 52 | 53 | 从而实现对 Midi 的扩展和控制。 54 | 55 | #### 事件列表 56 | 57 | ``` 58 | /** 59 | * The INIT event occurs after a Midi instance is done being initialized 60 | */ 61 | PluginEvents::INIT = 'init'; 62 | 63 | /** 64 | * The COMMAND_CONFIGURE event occurs command is configure and lets you plugin some arguments & options. 65 | */ 66 | PluginEvents::COMMAND_CONFIGURE = 'command-configure'; 67 | 68 | /** 69 | * The PRE_COMMAND_RUN event occurs before a command is executed and lets you modify the input arguments/options 70 | */ 71 | PluginEvents::PRE_COMMAND_RUN = 'pre-command-run'; 72 | 73 | /** 74 | * The POST_COMMAND_RUN event occurs after a command is executed. 75 | * 76 | */ 77 | PluginEvents::POST_COMMAND_RUN = 'post-command-run'; 78 | 79 | /** 80 | * The PRE_KOALA_START event occurs before koala start and you check some dependency. 81 | */ 82 | PluginEvents::PRE_KOALA_START = 'pre-start-koala'; 83 | 84 | /** 85 | * The PRE_SESSIONS_SOLVING event occurs before solving sessions. 86 | */ 87 | PluginEvents::PRE_SESSIONS_SOLVING = 'pre-sessions-solving'; 88 | 89 | /** 90 | * The POST_SESSIONS_SOLVING event occurs after solving sessions. 91 | */ 92 | PluginEvents::POST_SESSIONS_SOLVING = 'post-sessions-solving'; 93 | 94 | /** 95 | * The POST_PARSE_SESSION event occurs after parse recorded session. 96 | */ 97 | PluginEvents::POST_PARSE_SESSION = 'post-parse-session'; 98 | 99 | /** 100 | * The PRE_REPLAY_SESSION event occurs before replay one session. 101 | */ 102 | PluginEvents::PRE_REPLAY_SESSION = 'pre-replay-session'; 103 | 104 | /** 105 | * The POST_REPLAY_SESSION event occurs after replay one session. 106 | */ 107 | PluginEvents::POST_REPLAY_SESSION = 'post-replay-session'; 108 | ``` 109 | 110 | ## Example 111 | 112 | DiPlugin 是一个滴滴的插件,同时包含 preload-plugin 和 plugin 两种插件。 113 | 114 | DiPlugin 的 `DiPlugin\ElasticPlugin` 是一个 preload-plugin 插件,提供对 run 命令的扩充,支持对 elastic 搜索录制的流量。 115 | 116 | DiPlugin 的 `DiPlugin\Plugin` 是一个 plugin 插件,主要是扩展和影响 Midi 的一些行为。 117 | 118 | 119 | -------------------------------------------------------------------------------- /doc/recorder/recorder.md: -------------------------------------------------------------------------------- 1 | # 录制流量 2 | 3 | ## 一、思路 4 | 5 | 注入 so 到 php-fpm,异步录制流量并存储,尽可能的减少对程序执行时间的影响。 6 | 7 | 注入 so 的方式,在 macOS 系统下通过 `DYLD_INSERT_LIBRARIES`,Linux 系统下通过 `LD_PRELOAD` 来实现。 8 | 9 | 简单示例: 10 | 11 | ``` 12 | # macOS 13 | $ DYLD_INSERT_LIBRARIES="/usr/local/var/koala/koala-libc.so:/usr/lib/libcurl.dylib" DYLD_FORCE_FLAT_NAMESPACE="y" LC_CTYPE="C" KOALA_SO=/usr/local/var/koala/koala-recorder.so KOALA_RECORD_TO_DIR=/usr/local/var/koala /usr/local/sbin/php-fpm 14 | 15 | # or, Linux 16 | $ LD_PRELOAD="/usr/local/var/koala/koala-libc.so /usr/lib64/libcurl.so.4" LC_CTYPE="C" KOALA_SO=/usr/local/var/koala/koala-recorder.so KOALA_RECORD_TO_DIR=/usr/local/var/koala /usr/local/sbin/php-fpm 17 | ``` 18 | 19 | 示例见 [PHP DEMO](./../../example/php/README.md)。 20 | 21 | 在滴滴内部,每个模块会有 1-2 台机器,进行线上环境录制。录制已经在生产环境使用。 22 | 23 | ## 二、环境变量 24 | 25 | 录制使用的环境变量有: 26 | 27 | `KOALA_SO`: 值是 koala-recorder.so 文件的路径,用于 koala-libc.so 读取此环境变量,来动态加载 so。 28 | 29 | `KOALA_RECORD_TO_DIR`: 值是存储录制流量的目录,录制好的流量,将以文件的形式,存储在这个目录下。 30 | 31 | `KOALA_RECORD_TO_ES`: 与 `KOALA_RECORD_TO_DIR` 相对,两者取其一,这个环境变量指定存储 ES 的 Url,即把录制好的流量,通过这个 Url 写入到 ES 里。 32 | 33 | 具体代码实现,可查看 `koala/cmd/recorder/main.go`。 34 | 35 | ## 三、自定义录制 36 | 37 | Koala 是一个库,`koala/cmd/recorder/main.go` 是在 Koala 基础上实现的一个简单版本的录制。 38 | 39 | 可参考 `koala/cmd/recorder/main.go` 代码,实现自定义的 recorder。 40 | 41 | 譬如,对录制的流量进行过滤(只录制指定文件、指定端口的流量等)、控制不同接口的录制频率、使用其他存储方式等。 42 | 43 | ## 四、录制支持 44 | 45 | 目前录制支持 HTTP、Redis、MySQL、Apcu、UDP、Thrift 等协议。 46 | 47 | HTTP、Redis、MySQL、UDP、Thrift 属于网络流量录制,与之类似的,都可以录制。 48 | 49 | Apcu 录制,需要对 Apcu 接口进行封装来实现录制上报,见示例代码 [DEMO](./../../example/php/apcu.php)。 50 | 51 | ## 五、注意事项 52 | 53 | - 划分多个请求的流量 54 | 55 | 通常 php-fpm 是 master & worker 模式,worker 是单进程单线程,串行处理请求。 56 | 57 | Nginx 和 php-fpm 之间使用 fastcgi 协议。 58 | 59 | 所以,请求之间可以通过 fastcgi 开始请求的协议来划分请求。 60 | 61 | 下面是一个 fastcgi 协议的示例,设置 sut.InboundRequestPrefix 为 fastcgi 开始请求的协议前两个字节,来辅助判断是否来新请求。 62 | 63 | ```golang 64 | // 文件 `koala/cmd/recorder/main.go` 65 | protocol := envarg.GetenvFromC("KOALA_INBOUND_PROTOCOL") 66 | if protocol == "fastcgi" || protocol == "" { 67 | sut.InboundRequestPrefix = []byte{1, 1} 68 | } 69 | ``` 70 | 71 | 如果来新请求,就结束上一次请求的录制。 72 | 73 | 所以,在录制的时候,第一个请求访问后,并没有发现有流量被录制下来。 74 | 75 | 当第二个请求过来时,才会结束第一个请求的录制,并存储录下来的流量。 76 | 77 | 如果是存储到文件的话,第二个请求访问时,才会看到第一个请求的录制文件。 78 | 79 | 80 | -------------------------------------------------------------------------------- /example/php/apcu.php: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | ./ 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /example/php/report-coverage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/example/php/report-coverage.png -------------------------------------------------------------------------------- /example/php/report-trace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/example/php/report-trace.png -------------------------------------------------------------------------------- /example/php/report-upstream.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/example/php/report-upstream.png -------------------------------------------------------------------------------- /example/php/report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/example/php/report.png -------------------------------------------------------------------------------- /example/php/xdebug-at-breakpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/example/php/xdebug-at-breakpoint.png -------------------------------------------------------------------------------- /example/php/xdebug-breakpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/example/php/xdebug-breakpoint.png -------------------------------------------------------------------------------- /example/php/xdebug-mapping.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/example/php/xdebug-mapping.png -------------------------------------------------------------------------------- /koala-libc/.gitignore: -------------------------------------------------------------------------------- 1 | /output 2 | *.so 3 | -------------------------------------------------------------------------------- /koala-libc/README.md: -------------------------------------------------------------------------------- 1 | # koala-libc 2 | 3 | [Old Version](https://github.com/v2pro/koala-libc) 4 | 5 | If koala-recorder.so is loaded on php-fpm master process, fork() will break golang. 6 | Use koala-libc.so to load koala-recorder.so in the child process to circumvent this problem. 7 | 8 | ``` 9 | # compile https://github.com/v2pro/koala/tree/master/gateway/gw4libc to ~/koala-recorder.so 10 | # compile https://github.com/v2pro/koala-libc to ~/koala-libc.so 11 | KOALA_SO=~/koala-recorder.so LD_PRELOAD="~/koala-libc.so /usr/lib/x86_64-linux-gnu/libcurl.so.4" /usr/sbin/php-fpm7.0 -F 12 | # ~/koala-libc.so will be loaded in master process 13 | # ~/koala-recorder.so will be loaded in child process, at the first call to accept() 14 | ``` 15 | 16 | # build 17 | 18 | * ./build.sh -> koala-libc.so 19 | 20 | -------------------------------------------------------------------------------- /koala-libc/allocated_string.h: -------------------------------------------------------------------------------- 1 | #ifndef __ALLOCATED_STRING_H__ 2 | #define __ALLOCATED_STRING_H__ 3 | 4 | struct ch_allocated_string { 5 | char *Ptr; 6 | size_t Len; 7 | }; 8 | 9 | #endif -------------------------------------------------------------------------------- /koala-libc/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | set -e 4 | RDEBUG=$(cd ../`dirname $0` && pwd -P) 5 | mkdir -p $RDEBUG/output/libs 6 | gcc -shared -fPIC hook.c -o $RDEBUG/output/libs/koala-libc.so -ldl -std=c99 7 | echo "Finish compiled koala-libc to $RDEBUG/output/libs/koala-libc.so" 8 | -------------------------------------------------------------------------------- /koala-libc/countlog.h: -------------------------------------------------------------------------------- 1 | #ifndef __COUNTLOG_H__ 2 | #define __COUNTLOG_H__ 3 | 4 | # include 5 | 6 | enum event_level { trace=10, debug=20, info = 30, warn = 40, error = 50, fatal = 60 }; 7 | enum event_arg_type { STRING=1, UNSIGNED_LONG=2 }; 8 | 9 | struct event_arg { 10 | enum event_arg_type Type; 11 | const char *Val_string; 12 | unsigned long Val_ulong; 13 | }; 14 | 15 | struct event_arg cl_str(const char *val) { 16 | struct event_arg _ = { STRING, val, strlen(val) }; 17 | return _; 18 | } 19 | 20 | struct event_arg cl_ulong(unsigned long val) { 21 | struct event_arg _ = { UNSIGNED_LONG, 0, val }; 22 | return _; 23 | } 24 | 25 | typedef void (*countlog0_pfn_t)(pid_t, int, struct ch_span); 26 | static countlog0_pfn_t countlog0_func = NULL; 27 | 28 | typedef void (*countlog1_pfn_t)(pid_t, int, struct ch_span, struct ch_span, struct event_arg); 29 | static countlog1_pfn_t countlog1_func = NULL; 30 | 31 | void load_koala_so_countlog(void *koala_so_handle) { 32 | countlog0_func = (countlog0_pfn_t) dlsym(koala_so_handle, "countlog0"); 33 | countlog1_func = (countlog1_pfn_t) dlsym(koala_so_handle, "countlog1"); 34 | } 35 | 36 | static __thread pid_t _thread_id = 0; 37 | 38 | static pid_t get_thread_id() { 39 | if (_thread_id == 0) { 40 | #ifdef __APPLE__ 41 | uint64_t tid; 42 | pthread_threadid_np(NULL, &tid); 43 | _thread_id = (pid_t)tid; 44 | #else 45 | _thread_id = syscall(__NR_gettid); 46 | #endif 47 | } 48 | return _thread_id; 49 | } 50 | 51 | static void countlog0(enum event_level level, const char *event) { 52 | if (countlog0_func == NULL) { 53 | return; 54 | } 55 | struct ch_span event_span; 56 | event_span.Ptr = event; 57 | event_span.Len = strlen(event); 58 | countlog0_func(get_thread_id(), level, event_span); 59 | } 60 | 61 | static void countlog1(enum event_level level, const char *event, const char *k1, struct event_arg v1) { 62 | if (countlog1_func == NULL) { 63 | return; 64 | } 65 | struct ch_span event_span; 66 | event_span.Ptr = event; 67 | event_span.Len = strlen(event); 68 | struct ch_span k1_span; 69 | k1_span.Ptr = k1; 70 | k1_span.Len = strlen(k1); 71 | countlog1_func(get_thread_id(), level, event_span, k1_span, v1); 72 | } 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /koala-libc/span.h: -------------------------------------------------------------------------------- 1 | #ifndef __SPAN_H__ 2 | #define __SPAN_H__ 3 | 4 | struct ch_span { 5 | const void *Ptr; 6 | size_t Len; 7 | }; 8 | 9 | #endif -------------------------------------------------------------------------------- /koala/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | set -x 4 | 5 | RDEBUG=$(cd ../`dirname $0` && pwd -P) 6 | export GOPATH=/tmp/build-golang 7 | 8 | case $1 in 9 | "recorder" ) 10 | # record to file, only for testing purpose 11 | export CGO_CFLAGS="-DKOALA_LIBC_NETWORK_HOOK -DKOALA_LIBC_FILE_HOOK" 12 | export CGO_CPPFLAGS="-DKOALA_LIBC_NETWORK_HOOK -DKOALA_LIBC_FILE_HOOK" 13 | export CGO_CXXFLAGS="-std=c++11 -Wno-ignored-attributes" 14 | exec go build -tags="koala_recorder" -buildmode=c-shared -o $RDEBUG/output/libs/koala-recorder.so github.com/didi/rdebug/koala/cmd/recorder 15 | ;; 16 | "vendor" ) 17 | if [ ! -d /tmp/build-golang/src/github.com/didi/rdebug ]; then 18 | mkdir -p /tmp/build-golang/src/github.com/didi 19 | ln -s $RDEBUG /tmp/build-golang/src/github.com/didi/rdebug 20 | fi 21 | if [ ! -f "$GOPATH/bin/glide" ]; then 22 | go get github.com/Masterminds/glide 23 | fi 24 | cd /tmp/build-golang/src/github.com/didi/rdebug/koala 25 | exec $GOPATH/bin/glide i 26 | ;; 27 | esac 28 | 29 | # build replayer by default 30 | export CGO_CFLAGS="-DKOALA_LIBC_NETWORK_HOOK -DKOALA_LIBC_FILE_HOOK -DKOALA_LIBC_TIME_HOOK -DKOALA_LIBC_PATH_HOOK" 31 | export CGO_CPPFLAGS="-DKOALA_LIBC_NETWORK_HOOK -DKOALA_LIBC_FILE_HOOK -DKOALA_LIBC_TIME_HOOK -DKOALA_LIBC_PATH_HOOK" 32 | export CGO_CXXFLAGS="-std=c++11 -Wno-ignored-attributes" 33 | go build -tags="koala_replayer" -buildmode=c-shared -o $RDEBUG/output/libs/koala-replayer.so github.com/didi/rdebug/koala/cmd/replayer 34 | cp $RDEBUG/output/libs/koala-replayer.so $RDEBUG/php/midi/res/replayer/koala-replayer.so -------------------------------------------------------------------------------- /koala/cmd/replayer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "github.com/didi/rdebug/koala/gateway/gw4libc" 5 | "github.com/didi/rdebug/koala/envarg" 6 | "github.com/v2pro/plz/witch" 7 | ) 8 | 9 | func init() { 10 | envarg.SetupLogging() 11 | addrStr := envarg.GetenvFromC("KOALA_WITCH_ADDR") 12 | if addrStr == "" { 13 | addrStr = ":8318" 14 | } 15 | witch.Start(addrStr) 16 | } 17 | 18 | func main() { 19 | } 20 | -------------------------------------------------------------------------------- /koala/envarg/setup.go: -------------------------------------------------------------------------------- 1 | package envarg 2 | 3 | import ( 4 | "github.com/v2pro/plz/countlog" 5 | "os" 6 | ) 7 | 8 | func SetupLogging() { 9 | logWriter := countlog.NewAsyncLogWriter( 10 | LogLevel(), 11 | countlog.NewFileLogOutput(LogFile())) 12 | switch LogFormat() { 13 | case "HumanReadableFormat": 14 | logWriter.LogFormatter = &countlog.HumanReadableFormat{ 15 | ContextPropertyNames: []string{"threadID", "outboundSrc"}, 16 | StringLengthCap: 1024, 17 | } 18 | case "CompactFormat": 19 | logWriter.LogFormatter = &countlog.CompactFormat{StringLengthCap: 512} 20 | default: 21 | os.Stderr.WriteString("unknown LogFormat: " + LogFormat() + "\n") 22 | os.Stderr.Sync() 23 | logWriter.LogFormatter = &countlog.CompactFormat{} 24 | } 25 | logWriter.EventWhitelist["event!replaying.talks_scored"] = true 26 | //logWriter.EventWhitelist["event!sut.opening_file"] = true 27 | logWriter.Start() 28 | countlog.LogWriters = append(countlog.LogWriters, logWriter) 29 | } 30 | -------------------------------------------------------------------------------- /koala/envarg/tag_with_recorder.go: -------------------------------------------------------------------------------- 1 | // +build koala_recorder 2 | 3 | package envarg 4 | 5 | const isRecording = true 6 | -------------------------------------------------------------------------------- /koala/envarg/tag_with_replayer.go: -------------------------------------------------------------------------------- 1 | // +build koala_replayer 2 | 3 | package envarg 4 | 5 | const isReplaying = true 6 | -------------------------------------------------------------------------------- /koala/envarg/tag_without_recorder.go: -------------------------------------------------------------------------------- 1 | // +build !koala_recorder 2 | 3 | package envarg 4 | 5 | const isRecording = false 6 | -------------------------------------------------------------------------------- /koala/envarg/tag_without_replayer.go: -------------------------------------------------------------------------------- 1 | // +build !koala_replayer 2 | 3 | package envarg 4 | 5 | const isReplaying = false 6 | -------------------------------------------------------------------------------- /koala/gateway/README.md: -------------------------------------------------------------------------------- 1 | the entry point to koala world. 2 | -------------------------------------------------------------------------------- /koala/gateway/gw4libc/allocated_string.h: -------------------------------------------------------------------------------- 1 | #ifndef __ALLOCATED_STRING_H__ 2 | #define __ALLOCATED_STRING_H__ 3 | 4 | #include 5 | 6 | struct ch_allocated_string { 7 | char *Ptr; 8 | size_t Len; 9 | }; 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /koala/gateway/gw4libc/countlog.h: -------------------------------------------------------------------------------- 1 | #ifndef __COUNTLOG_H__ 2 | #define __COUNTLOG_H__ 3 | 4 | enum event_level { trace=10, debug=20, info = 30, warn = 40, error = 50, fatal = 60 }; 5 | enum event_arg_type { STRING=1, UNSIGNED_LONG=2 }; 6 | 7 | struct event_arg { 8 | enum event_arg_type Type; 9 | const char *Val_string; 10 | unsigned long Val_ulong; 11 | }; 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /koala/gateway/gw4libc/init.c: -------------------------------------------------------------------------------- 1 | static int _init; 2 | 3 | void go_initialized() { 4 | _init = 1; 5 | } 6 | 7 | int is_go_initialized() { 8 | return _init; 9 | } 10 | 11 | -------------------------------------------------------------------------------- /koala/gateway/gw4libc/init.h: -------------------------------------------------------------------------------- 1 | #ifndef __INIT_H__ 2 | #define __INIT_H__ 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | void go_initialized(); 9 | int is_go_initialized(); 10 | 11 | #ifdef __cplusplus 12 | } 13 | #endif 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /koala/gateway/gw4libc/interpose.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * Copyright (c) 2017 Charlie Curtsinger 4 | */ 5 | 6 | #if !defined(__INTERPOSE_HH) 7 | #define __INTERPOSE_HH 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | /// Function type inspection utility for interpose 15 | template struct fn_info { 16 | using type = F; 17 | using ret_type = int; 18 | }; 19 | 20 | /// Specialize the fn_info template for functions with non-void return types 21 | template struct fn_info { 22 | using type = R(Args...); 23 | using ret_type = R; 24 | }; 25 | 26 | #if defined(__linux__) 27 | 28 | /** 29 | * The linux interposition process uses weak aliases to replace the original function 30 | * and creates a real::___ function that will perform dynamic symbol resolution on the 31 | * first call. Be careful when interposing on memory allocation functions in particular; 32 | * simple operations like printing or symbol resolution could trigger another call to 33 | * malloc or calloc, which can cause unbounded recursion. 34 | */ 35 | #define INTERPOSE(NAME) \ 36 | namespace real { \ 37 | template \ 38 | auto NAME(Args... args) -> decltype(::NAME(args...)) { \ 39 | static bool initialized = false; \ 40 | static decltype(::NAME)* real_##NAME; \ 41 | if(!initialized) { \ 42 | real_##NAME = reinterpret_cast( \ 43 | reinterpret_cast(dlsym(RTLD_NEXT, #NAME))); \ 44 | __atomic_store_n(&initialized, true, __ATOMIC_RELEASE); \ 45 | } \ 46 | return real_##NAME(std::forward(args)...); \ 47 | } \ 48 | } \ 49 | extern "C" decltype(::NAME) NAME __attribute__((weak, alias("__interpose_" #NAME))); \ 50 | extern "C" fn_info::ret_type __interpose_##NAME 51 | 52 | #elif defined(__APPLE__) 53 | 54 | /// Structure exposed to the linker for interposition 55 | struct __osx_interpose { 56 | const void* new_func; 57 | const void* orig_func; 58 | }; 59 | 60 | /** 61 | * Generate a macOS interpose struct 62 | * Types from: http://opensource.apple.com/source/dyld/dyld-210.2.3/include/mach-o/dyld-interposing.h 63 | */ 64 | #define OSX_INTERPOSE_STRUCT(NEW, OLD) \ 65 | static const __osx_interpose __osx_interpose_##OLD \ 66 | __attribute__((used, section("__DATA, __interpose"))) = \ 67 | { reinterpret_cast(reinterpret_cast(&(NEW))), \ 68 | reinterpret_cast(reinterpret_cast(&(OLD))) } 69 | 70 | /** 71 | * The OSX interposition process is much simpler. Just create an OSX interpose struct, 72 | * include the actual function in the `real` namespace, and declare the beginning of the 73 | * replacement function with the appropriate return type. 74 | */ 75 | #define INTERPOSE(NAME) \ 76 | namespace real { \ 77 | using ::NAME; \ 78 | } \ 79 | extern "C" decltype(::NAME) __interpose_##NAME; \ 80 | OSX_INTERPOSE_STRUCT(__interpose_##NAME, NAME); \ 81 | extern "C" fn_info::ret_type __interpose_##NAME 82 | 83 | #endif 84 | 85 | #endif 86 | -------------------------------------------------------------------------------- /koala/gateway/gw4libc/span.h: -------------------------------------------------------------------------------- 1 | #ifndef __SPAN_H__ 2 | #define __SPAN_H__ 3 | 4 | #include 5 | 6 | struct ch_span { 7 | const void *Ptr; 8 | size_t Len; 9 | }; 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /koala/gateway/gw4libc/struct_ch_span.go: -------------------------------------------------------------------------------- 1 | package gw4libc 2 | 3 | // #include "span.h" 4 | import "C" 5 | import ( 6 | "math" 7 | "os" 8 | ) 9 | 10 | func ch_span_to_bytes(span C.struct_ch_span) []byte { 11 | buf := (*[math.MaxInt32]byte)(span.Ptr)[:span.Len] 12 | return buf 13 | } 14 | 15 | func ch_span_to_string(span C.struct_ch_span) string { 16 | buf := (*[math.MaxInt32]byte)(span.Ptr)[:span.Len] 17 | return string(buf) 18 | } 19 | 20 | func ch_span_to_open_flags(span C.struct_ch_span) int { 21 | buf := (*[math.MaxInt32]byte)(span.Ptr)[:span.Len] 22 | withPlus := 0 23 | withoutPlus := 0 24 | for _, b := range buf { 25 | switch b { 26 | case 'r': 27 | withoutPlus = os.O_RDONLY 28 | withPlus = os.O_RDWR 29 | case 'w': 30 | withoutPlus = os.O_WRONLY | os.O_CREATE | os.O_TRUNC 31 | withPlus = os.O_RDWR | os.O_CREATE | os.O_TRUNC 32 | case 'a': 33 | withoutPlus = os.O_WRONLY | os.O_CREATE | os.O_APPEND 34 | withPlus = os.O_RDWR | os.O_CREATE | os.O_APPEND 35 | case 'b': 36 | // ignore 37 | case '+': 38 | return withPlus 39 | default: 40 | break 41 | } 42 | } 43 | return withoutPlus 44 | } 45 | -------------------------------------------------------------------------------- /koala/gateway/gw4libc/tag_without_darwin.go: -------------------------------------------------------------------------------- 1 | // +build !darwin 2 | 3 | package gw4libc 4 | 5 | // #cgo LDFLAGS: -lrt 6 | import "C" 7 | -------------------------------------------------------------------------------- /koala/gateway/gw4libc/thread_id.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include 4 | #include 5 | #include 6 | #include "thread_id.h" 7 | 8 | pid_t get_thread_id() { 9 | #ifdef __APPLE__ 10 | uint64_t tid; 11 | pthread_threadid_np(NULL, &tid); 12 | return (pid_t)tid; 13 | #else 14 | return syscall(__NR_gettid); 15 | #endif 16 | } 17 | -------------------------------------------------------------------------------- /koala/gateway/gw4libc/thread_id.h: -------------------------------------------------------------------------------- 1 | #ifndef __THREAD_ID_H__ 2 | #define __THREAD_ID_H__ 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | pid_t get_thread_id(); 11 | 12 | #ifdef __cplusplus 13 | } 14 | #endif 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /koala/gateway/gw4libc/time_hook.cpp: -------------------------------------------------------------------------------- 1 | #ifdef KOALA_LIBC_TIME_HOOK 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "interpose.h" 9 | #include "time_hook.h" 10 | 11 | extern "C" { 12 | static int offset = 0; 13 | void set_time_offset(int val) { 14 | offset = val; 15 | } 16 | } 17 | 18 | INTERPOSE(time)(time_t *time_tptr) { 19 | time_t my_time; 20 | auto result = real::time(&my_time); 21 | result += offset; 22 | if (time_tptr != NULL) { 23 | *time_tptr = result; 24 | } 25 | return result; 26 | } 27 | 28 | #ifdef __APPLE__ 29 | 30 | #else 31 | INTERPOSE(gettimeofday)(struct timeval *tv, struct timezone *tz) { 32 | auto result = real::gettimeofday(tv, tz); 33 | if (tv != NULL) { 34 | tv->tv_sec = tv->tv_sec + offset; 35 | } 36 | return result; 37 | } 38 | INTERPOSE(clock_gettime)(clockid_t clk_id, struct timespec *tp) { 39 | auto result = real::clock_gettime(clk_id, tp); 40 | if (tp != NULL) { 41 | tp->tv_sec = tp->tv_sec + offset; 42 | } 43 | return result; 44 | } 45 | INTERPOSE(ftime)(struct timeb *tb) { 46 | auto result = real::ftime(tb); 47 | if (tb != NULL) { 48 | tb->time = tb->time + offset; 49 | } 50 | return result; 51 | } 52 | #endif 53 | #else 54 | extern "C" { 55 | void set_time_offset(int val) { 56 | } 57 | } 58 | #endif // KOALA_LIBC_TIME_HOOK 59 | -------------------------------------------------------------------------------- /koala/gateway/gw4libc/time_hook.h: -------------------------------------------------------------------------------- 1 | #ifndef __TIME_HOOK_H__ 2 | #define __TIME_HOOK_H__ 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | void set_time_offset(int offset); 9 | 10 | #ifdef __cplusplus 11 | } 12 | #endif 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /koala/gateway/gw4libc/version.c: -------------------------------------------------------------------------------- 1 | char* library_version = { "KOALA-VERSION: 3.0.0" }; 2 | -------------------------------------------------------------------------------- /koala/glide.yaml: -------------------------------------------------------------------------------- 1 | package: github.com/didi/rdebug/koala 2 | import: 3 | - package: github.com/json-iterator/go 4 | version: ff2b70c1dbffdd98567bd8c2f9449d97c0d04c88 5 | - package: github.com/v2pro/plz 6 | version: 03cb46d336e021ccc579800e033a9a48d09a87d0 7 | subpackages: 8 | - countlog 9 | - witch 10 | - statik 11 | testImport: 12 | - package: github.com/stretchr/testify 13 | version: ^1.1.4 14 | subpackages: 15 | - require 16 | -------------------------------------------------------------------------------- /koala/internal/tag_with_koala_go.go: -------------------------------------------------------------------------------- 1 | // +build koala_go 2 | 3 | package internal 4 | 5 | import ( 6 | "runtime" 7 | "github.com/v2pro/plz/countlog" 8 | "syscall" 9 | "net" 10 | ) 11 | 12 | func SetCurrentGoRoutineIsKoala() { 13 | countlog.Trace("event!internal.set_is_koala", "threadID", GetCurrentGoRoutineId()) 14 | runtime.SetCurrentGoRoutineIsKoala() 15 | } 16 | 17 | func SetDelegatedFromGoRoutineId(goid int64) { 18 | countlog.Debug("event!internal.set_delegated_from", 19 | "from", goid, "to", GetCurrentGoRoutineId()) 20 | runtime.SetDelegatedFromGoRoutineId(goid) 21 | } 22 | 23 | func GetCurrentGoRoutineIsKoala() bool { 24 | return runtime.GetCurrentGoRoutineIsKoala() 25 | } 26 | 27 | func GetCurrentGoRoutineId() int64 { 28 | return runtime.GetCurrentGoRoutineId() 29 | } 30 | 31 | func RegisterOnConnect(callback func(fd int, sa syscall.Sockaddr)) { 32 | syscall.OnConnect = callback 33 | } 34 | 35 | func RegisterOnAccept(callback func(serverSocketFD int, clientSocketFD int, sa syscall.Sockaddr)) { 36 | syscall.OnAccept = callback 37 | } 38 | 39 | func RegisterOnRecv(callback func(fd int, net string, raddr net.Addr, span []byte)) { 40 | net.OnRead = callback 41 | } 42 | 43 | func RegisterOnSend(callback func(fd int, net string, raddr net.Addr, span []byte)) { 44 | net.OnWrite = callback 45 | } 46 | 47 | func RegisterOnClose(callback func(fd int)) { 48 | net.OnClose = callback 49 | } 50 | 51 | func RegisterOnGoRoutineExit(callback func(goid int64)) { 52 | runtime.OnGoRoutineExit = callback 53 | } 54 | -------------------------------------------------------------------------------- /koala/internal/tag_with_original_go.go: -------------------------------------------------------------------------------- 1 | // +build !koala_go 2 | 3 | package internal 4 | 5 | import ( 6 | "syscall" 7 | "net" 8 | ) 9 | 10 | func SetCurrentGoRoutineIsKoala() { 11 | } 12 | 13 | func SetDelegatedFromGoRoutineId(goid int64) { 14 | } 15 | 16 | func GetCurrentGoRoutineIsKoala() bool { 17 | return false 18 | } 19 | 20 | func GetCurrentGoRoutineId() int64 { 21 | return 0 22 | } 23 | 24 | func RegisterOnConnect(callback func(fd int, sa syscall.Sockaddr)) { 25 | } 26 | 27 | func RegisterOnAccept(callback func(serverSocketFD int, clientSocketFD int, sa syscall.Sockaddr)) { 28 | } 29 | 30 | func RegisterOnRecv(callback func(fd int, net string, raddr net.Addr, span []byte)) { 31 | } 32 | 33 | func RegisterOnSend(callback func(fd int, net string, raddr net.Addr, span []byte)) { 34 | } 35 | 36 | func RegisterOnClose(callback func(fd int)) { 37 | } 38 | 39 | func RegisterOnGoRoutineExit(callback func(goid int64)) { 40 | } 41 | -------------------------------------------------------------------------------- /koala/koala.go: -------------------------------------------------------------------------------- 1 | // public api for go application using koala 2 | package koala 3 | 4 | import ( 5 | "github.com/didi/rdebug/koala/internal" 6 | ) 7 | 8 | // SetDelegatedFromGoRoutineId should be used when this goroutine is doing work for another goroutine, 9 | // for example multiplex protocol, the request is generated in one goroutine, but sent out from another one. 10 | // Tracking the work delegation chain is required to record or replay session. 11 | func SetDelegatedFromGoRoutineId(goid int64) { 12 | internal.SetDelegatedFromGoRoutineId(goid) 13 | } 14 | 15 | // GetCurrentGoRoutineId get goid from the g 16 | func GetCurrentGoRoutineId() int64 { 17 | return internal.GetCurrentGoRoutineId() 18 | } 19 | 20 | func ExcludeCurrentGoRoutineFromRecording() { 21 | internal.SetCurrentGoRoutineIsKoala() 22 | } 23 | -------------------------------------------------------------------------------- /koala/outbound/http.go: -------------------------------------------------------------------------------- 1 | package outbound 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "github.com/v2pro/plz/countlog" 7 | ) 8 | 9 | var http100req = []byte("Expect: 100-continue") 10 | var http100resp = []byte("HTTP/1.1 100 Continue\r\n\r\n") 11 | 12 | func simulateHttp(ctx context.Context, request []byte) []byte { 13 | if bytes.Contains(request, http100req) { 14 | return simulateHttp100(ctx, request) 15 | } 16 | return nil 17 | } 18 | 19 | func simulateHttp100(ctx context.Context, request []byte) []byte { 20 | countlog.Debug("event!outbound.simulated_http", 21 | "ctx", ctx, 22 | "requestKeyword", "100-continue", 23 | "content", request) 24 | return http100resp 25 | } 26 | -------------------------------------------------------------------------------- /koala/outbound/mysql.go: -------------------------------------------------------------------------------- 1 | package outbound 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/hex" 7 | "github.com/v2pro/plz/countlog" 8 | ) 9 | 10 | var mysqlGreeting []byte 11 | var maxAllowedPacketResp []byte 12 | 13 | func init() { 14 | mysqlGreeting, _ = hex.DecodeString("4e0000000a352e372e31362d6c6f67009f943f003663266e4a62520d00fff7530200ff8115000000000000000000003e5529546501277e7c430f1a006d7973716c5f6e61746976655f70617373776f726400") 15 | maxAllowedPacketResp, _ = hex.DecodeString( 16 | "0100000101" + 17 | "2a000002036465660000001440406d61785f616c6c6f7765645f7061636b6574000c3f001500000008a000000000" + 18 | "05000003fe00000200" + 19 | "09000004083637313038383634" + 20 | "05000005fe00000200") 21 | } 22 | 23 | func simulateMysql(ctx context.Context, request []byte) []byte { 24 | resp := _simulateMysql(ctx, request) 25 | if resp != nil { 26 | resp[3] = request[3] + 1 27 | } 28 | return resp 29 | } 30 | 31 | func _simulateMysql(ctx context.Context, request []byte) []byte { 32 | if bytes.Index(request, []byte("mysql_native_password")) != -1 { 33 | countlog.Debug("event!outbound.simulated_mysql", 34 | "ctx", ctx, 35 | "requestKeyword", "mysql_native_password", 36 | "content", request) 37 | return []byte{0x07, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00} 38 | } 39 | 40 | if bytes.Index(request, []byte("@@max_allowed_packet")) != -1 { 41 | countlog.Debug("event!outbound.simulated_mysql", 42 | "ctx", ctx, 43 | "requestKeyword", "@@max_allowed_packet", 44 | "content", request) 45 | return maxAllowedPacketResp 46 | } 47 | 48 | if bytes.Index(request, []byte("SET NAMES utf8")) != -1 { 49 | countlog.Debug("event!outbound.simulated_mysql", 50 | "requestKeyword", "SET NAMES utf8", 51 | "ctx", ctx, 52 | "content", request) 53 | return []byte{0x07, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00} 54 | } 55 | 56 | if bytes.Index(request, []byte("START TRANSACTION")) != -1 { 57 | countlog.Debug("event!outbound.simulated_mysql", 58 | "requestKeyword", "START TRANSACTION", 59 | "ctx", ctx, 60 | "content", request) 61 | return []byte{0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00} 62 | } 63 | return nil 64 | } 65 | -------------------------------------------------------------------------------- /koala/recording/action_test.go: -------------------------------------------------------------------------------- 1 | package recording 2 | 3 | import ( 4 | "testing" 5 | "github.com/stretchr/testify/require" 6 | "encoding/json" 7 | ) 8 | 9 | func Test_marshal_append_file(t *testing.T) { 10 | should := require.New(t) 11 | bytes, err := json.Marshal(&AppendFile{ 12 | FileName: "/abc", 13 | Content: []byte("hello"), 14 | }) 15 | should.Nil(err) 16 | should.Contains(string(bytes), "hello") 17 | } 18 | 19 | func Test_marshal_call_outbound(t *testing.T) { 20 | should := require.New(t) 21 | bytes, err := json.Marshal(&CallOutbound{ 22 | Request: []byte("hello"), 23 | Response: []byte("world"), 24 | }) 25 | should.Nil(err) 26 | should.Contains(string(bytes), "hello") 27 | should.Contains(string(bytes), "world") 28 | } 29 | 30 | func Test_marshal_return_inbound(t *testing.T) { 31 | should := require.New(t) 32 | bytes, err := json.Marshal(&ReturnInbound{ 33 | Response: []byte("hello"), 34 | }) 35 | should.Nil(err) 36 | should.Contains(string(bytes), "hello") 37 | } 38 | 39 | func Test_marshal_call_from_inbound(t *testing.T) { 40 | should := require.New(t) 41 | bytes, err := json.Marshal(&CallFromInbound{ 42 | Request: []byte("hello"), 43 | }) 44 | should.Nil(err) 45 | should.Contains(string(bytes), "hello") 46 | } 47 | 48 | func Test_marshal_session(t *testing.T) { 49 | session := Session{ 50 | CallFromInbound: &CallFromInbound{ 51 | Request: []byte("hello"), 52 | }, 53 | ReturnInbound: &ReturnInbound{ 54 | Response: []byte("hello"), 55 | }, 56 | Actions: []Action{ 57 | &CallOutbound{ 58 | Request: []byte("hello"), 59 | Response: []byte("world"), 60 | }, 61 | &AppendFile{ 62 | FileName: "/abc", 63 | Content: []byte("hello"), 64 | }, 65 | }, 66 | } 67 | bytes, err := json.MarshalIndent(session, "", " ") 68 | should := require.New(t) 69 | should.Nil(err) 70 | should.NotContains(string(bytes), "=") // no base64 71 | } 72 | 73 | func Test_encode_any_byte_array(t *testing.T) { 74 | should := require.New(t) 75 | should.Equal(`"hello"`, string(EncodeAnyByteArray([]byte("hello")))) 76 | should.Equal(`"hel\nlo"`, string(EncodeAnyByteArray([]byte("hel\nlo")))) 77 | should.Equal(`"hel\rlo"`, string(EncodeAnyByteArray([]byte("hel\rlo")))) 78 | should.Equal(`"hel\tlo"`, string(EncodeAnyByteArray([]byte("hel\tlo")))) 79 | should.Equal(`"hel\"lo"`, string(EncodeAnyByteArray([]byte("hel\"lo")))) 80 | should.Equal(`"hel\\x5cx00lo"`, string(EncodeAnyByteArray([]byte(`hel\x00lo`)))) 81 | should.Equal(`"hel\\x00lo"`, string(EncodeAnyByteArray([]byte("hel\u0000lo")))) 82 | should.Equal(`"\\x01\\x02\\x03"`, string(EncodeAnyByteArray([]byte{1, 2, 3}))) 83 | should.Equal(`"中文"`, string(EncodeAnyByteArray([]byte("中文")))) 84 | should.Equal(`"\\xef\\xbf\\xbdBEEF"`, 85 | string(EncodeAnyByteArray([]byte{239, 191, 189, 66, 69, 69, 70}))) 86 | } 87 | -------------------------------------------------------------------------------- /koala/recording/async.go: -------------------------------------------------------------------------------- 1 | package recording 2 | 3 | import ( 4 | "github.com/v2pro/plz/countlog" 5 | "context" 6 | ) 7 | 8 | type AsyncRecorder struct { 9 | Context context.Context 10 | realRecorder Recorder 11 | recordChan chan *Session 12 | } 13 | 14 | func NewAsyncRecorder(realRecorder Recorder) *AsyncRecorder { 15 | return &AsyncRecorder{ 16 | recordChan: make(chan *Session, 100), 17 | realRecorder: realRecorder, 18 | } 19 | } 20 | 21 | func (recorder *AsyncRecorder) Start() { 22 | go recorder.backgroundRecord() 23 | } 24 | 25 | func (recorder *AsyncRecorder) backgroundRecord() { 26 | defer func() { 27 | recovered := recover() 28 | if recovered != nil { 29 | countlog.Error("event!recording.panic", 30 | "err", recovered, 31 | "ctx", recorder.Context, 32 | "stacktrace", countlog.ProvideStacktrace) 33 | } 34 | }() 35 | for { 36 | session := <-recorder.recordChan 37 | countlog.Debug("event!recording.record_session", 38 | "ctx", recorder.Context, 39 | "session", session) 40 | recorder.realRecorder.Record(session) 41 | } 42 | } 43 | 44 | func (recorder *AsyncRecorder) Record(session *Session) { 45 | select { 46 | case recorder.recordChan <- session: 47 | default: 48 | countlog.Error("event!recording.record_chan_overflow", 49 | "ctx", recorder.Context) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /koala/recording/recorder.go: -------------------------------------------------------------------------------- 1 | package recording 2 | 3 | type Recorder interface { 4 | Record(session *Session) 5 | } 6 | 7 | var Recorders = []Recorder{} 8 | 9 | var ShouldRecordAction = func(action Action) bool { 10 | return true 11 | } 12 | -------------------------------------------------------------------------------- /koala/recording/talk.go: -------------------------------------------------------------------------------- 1 | package recording 2 | 3 | import "net" 4 | 5 | type Talk struct { 6 | Peer net.TCPAddr 7 | Request []byte 8 | ResponseTime int64 9 | Response []byte 10 | } 11 | -------------------------------------------------------------------------------- /koala/recording/trace_header_test.go: -------------------------------------------------------------------------------- 1 | package recording 2 | 3 | import ( 4 | "testing" 5 | "github.com/stretchr/testify/require" 6 | ) 7 | 8 | func Test_trace_header(t *testing.T) { 9 | should := require.New(t) 10 | header := TraceHeader{} 11 | header = header.Set(TraceHeaderKeySpanId, TraceHeaderValue("world")) 12 | header = header.Set(TraceHeaderKeyTraceId, TraceHeaderValue("hello")) 13 | originalHeader := append([]byte(nil), header...) 14 | var key TraceHeaderKey 15 | var value TraceHeaderValue 16 | key, value, header = header.Next() 17 | should.Equal(TraceHeaderKeyTraceId, key) 18 | should.Equal(TraceHeaderValue("hello"), value) 19 | key, value, header = header.Next() 20 | should.Equal(TraceHeaderKeySpanId, key) 21 | should.Equal(TraceHeaderValue("world"), value) 22 | should.Len(header, 0) 23 | header = TraceHeader(originalHeader).Set(TraceHeaderKeySpanId, TraceHeaderValue("world2")) 24 | key, value, header = header.Next() 25 | should.Equal(TraceHeaderKeySpanId, key) 26 | should.Equal(TraceHeaderValue("world2"), value) 27 | } 28 | -------------------------------------------------------------------------------- /koala/replaying/README.md: -------------------------------------------------------------------------------- 1 | pass session from inbound => sut => outbound 2 | -------------------------------------------------------------------------------- /koala/replaying/lexer/lexer.go: -------------------------------------------------------------------------------- 1 | package lexer 2 | 3 | import ( 4 | "bytes" 5 | "unicode" 6 | ) 7 | 8 | type Lexer interface { 9 | Name() string 10 | Lex(req []byte) ([]string, error) 11 | Lex2Vector(text []byte) (map[string]float64, error) 12 | } 13 | 14 | type ProtocolLexer interface { 15 | Lexer 16 | Match(req []byte) bool 17 | } 18 | 19 | type DefaultLexer struct { 20 | ReadableChunkSize int 21 | } 22 | 23 | func (d *DefaultLexer) Name() string { 24 | return "DEFAULT_LEXER" 25 | } 26 | 27 | func (d *DefaultLexer) Match(text []byte) bool { 28 | return true 29 | } 30 | 31 | func (d *DefaultLexer) Lex(text []byte) ([]string, error) { 32 | var chunks []string 33 | 34 | offset := 0 35 | for { 36 | strikeStart, strikeLen := findReadableChunk(text[offset:]) 37 | if strikeStart == -1 { 38 | break 39 | } 40 | for i := offset; i < offset+strikeStart; i += 1 { 41 | chunks = append(chunks, string(text[i:i+1])) // unreadable char 42 | } 43 | if strikeLen > d.ReadableChunkSize { 44 | strikeLen = d.ReadableChunkSize 45 | } 46 | chunks = append(chunks, string(text[offset+strikeStart:offset+strikeStart+strikeLen])) // readable 47 | offset += strikeStart + strikeLen 48 | } 49 | 50 | keyLen := len(text) 51 | for i := offset; i < keyLen; i += 1 { 52 | chunks = append(chunks, string(text[i:i+1])) // unreadable char 53 | } 54 | 55 | return chunks, nil 56 | } 57 | 58 | func (d *DefaultLexer) Lex2Vector(text []byte) (map[string]float64, error) { 59 | vector := make(map[string]float64, 32) 60 | 61 | offset := 0 62 | for { 63 | strikeStart, strikeLen := findReadableChunk(text[offset:]) 64 | if strikeStart == -1 { 65 | break 66 | } 67 | // unreadable char 68 | for i := offset; i < offset+strikeStart; i += 1 { 69 | token := string(text[i:i+1]) 70 | if _, ok := vector[token]; ok { 71 | vector[token] += 1 72 | } else { 73 | vector[token] = 1 74 | } 75 | } 76 | // readable 77 | if strikeLen > d.ReadableChunkSize { 78 | strikeLen = d.ReadableChunkSize 79 | } 80 | token := string(text[offset+strikeStart:offset+strikeStart+strikeLen]) 81 | if _, ok := vector[token]; ok { 82 | vector[token] += float64(len(token)) 83 | } else { 84 | vector[token] = float64(len(token)) 85 | } 86 | offset += strikeStart + strikeLen 87 | } 88 | 89 | // unreadable char 90 | keyLen := len(text) 91 | for i := offset; i < keyLen; i += 1 { 92 | token := string(text[i:i+1]) 93 | if _, ok := vector[token]; ok { 94 | vector[token] += 1 95 | } else { 96 | vector[token] = 1 97 | } 98 | } 99 | 100 | return vector, nil 101 | } 102 | 103 | func Default() *DefaultLexer { 104 | return &DefaultLexer{ReadableChunkSize: 128} 105 | } 106 | 107 | func findReadableChunk(key []byte) (int, int) { 108 | start := bytes.IndexFunc(key, func(r rune) bool { 109 | // A-Z a-z . _ 110 | return unicode.IsNumber(r) || unicode.IsLetter(r) || r == 46 || r == 95 111 | }) 112 | if start == -1 { 113 | return -1, -1 114 | } 115 | end := bytes.IndexFunc(key[start:], func(r rune) bool { 116 | return !(unicode.IsNumber(r) || unicode.IsLetter(r) || r == 46 || r == 95) 117 | }) 118 | if end == -1 { 119 | return start, len(key) - start 120 | } 121 | return start, end 122 | } 123 | -------------------------------------------------------------------------------- /koala/replaying/lexer/lexer_http.go: -------------------------------------------------------------------------------- 1 | // simple protocol lexer, parse and use to add vector's weight 2 | // keep balance between preciseness and speed 3 | 4 | package lexer 5 | 6 | import ( 7 | "bytes" 8 | "regexp" 9 | "strings" 10 | ) 11 | 12 | var httpReqRegex = regexp.MustCompile(`^(GET|POST|PUT|DELETE|HEAD|OPTIONS|TRACE|CONNECT) (.*) HTTP/1\.[10]`) 13 | 14 | type HTTPLexer struct { 15 | DefaultLexer 16 | } 17 | 18 | func (h *HTTPLexer) Name() string { 19 | return "HTTP_LEXER" 20 | } 21 | 22 | func (h *HTTPLexer) Match(req []byte) bool { 23 | return httpReqRegex.Match(req) 24 | } 25 | 26 | func (h *HTTPLexer) Lex(text []byte) ([]string, error) { 27 | return h.DefaultLexer.Lex(text) 28 | } 29 | 30 | // request line(uri & path) have more weight: weight * headerLines 31 | func (h *HTTPLexer) Lex2Vector(text []byte) (map[string]float64, error) { 32 | headerLines := 0 33 | valuableTokens := []string{} 34 | 35 | reqLine := text 36 | idx := bytes.IndexByte(reqLine, '\n') 37 | if idx > 0 { 38 | header := reqLine[idx+1:] 39 | reqLine = reqLine[:idx] 40 | for len(header) > 0 { 41 | i := bytes.IndexByte(header, '\n') 42 | if i < 3 { 43 | break 44 | } 45 | headerLines++ 46 | header = header[i+1:] 47 | } 48 | } 49 | 50 | strReqLine := strings.TrimSpace(string(reqLine)) 51 | _, uri, _, ok := parseRequestLine(strReqLine) 52 | if ok { 53 | valuableTokens = append(valuableTokens, uri) 54 | for _, path := range strings.Split(uri, "/") { 55 | if len(path) > 0 { 56 | valuableTokens = append(valuableTokens, path) 57 | } 58 | } 59 | } else { 60 | valuableTokens = append(valuableTokens, strReqLine) 61 | } 62 | 63 | vector, _ := h.DefaultLexer.Lex2Vector(text) 64 | for _, token := range valuableTokens { 65 | addWeight := float64(len(token)*(1+headerLines)) 66 | if _, ok := vector[token]; ok { 67 | vector[token] += addWeight 68 | } else { 69 | vector[token] = addWeight 70 | } 71 | } 72 | 73 | return vector, nil 74 | } 75 | 76 | func NewHTTPLexer() *HTTPLexer { 77 | return &HTTPLexer{DefaultLexer{ReadableChunkSize:128}} 78 | } 79 | 80 | func parseRequestLine(line string) (method, requestURI, proto string, ok bool) { 81 | s1 := strings.Index(line, " ") 82 | s2 := strings.Index(line[s1+1:], " ") 83 | if s1 < 0 || s2 < 0 { 84 | return 85 | } 86 | s2 += s1 + 1 87 | 88 | uri := line[s1+1 : s2] 89 | t2 := strings.Index(uri, "?") 90 | if t2 > 0 { 91 | uri = uri[:t2] 92 | } 93 | return line[:s1], uri, line[s2+1:], true 94 | } -------------------------------------------------------------------------------- /koala/replaying/lexer/lexer_http_test.go: -------------------------------------------------------------------------------- 1 | package lexer 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "net/http" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestHTTPLexer_Match(t *testing.T) { 14 | should := require.New(t) 15 | lexer := HTTPLexer{} 16 | 17 | should.True(lexer.Match([]byte("GET /hello HTTP/1.0"))) 18 | should.False(lexer.Match([]byte("GET /hello HTTP/1.5"))) 19 | should.False(lexer.Match([]byte("GET /helloHTTP/1.0"))) 20 | should.False(lexer.Match([]byte("GETT /hello HTTP/1.0"))) 21 | should.False(lexer.Match([]byte("/hello HTTP/1.0"))) 22 | } 23 | 24 | func TestHTTPLexer_Lex(t *testing.T) { 25 | req := `POST /v1/geo/Fence?abc=foo HTTP/1.1 26 | Host: 10.69.2.4:8000 27 | Accept: */* 28 | didi-header-rid: 6 29 | Content-Type: application/x-www-form-urlencoded 30 | 31 | flat=22&feature=0&sign=&version=1.0.0` 32 | 33 | request, err := http.ReadRequest(bufio.NewReader(strings.NewReader(req))) 34 | if err != nil { 35 | fmt.Println(err) 36 | } 37 | fmt.Println(request.Method) 38 | } 39 | 40 | func TestHTTPLexer_Lex2Vector(t *testing.T) { 41 | lexer := HTTPLexer{} 42 | req := `POST /v1/geo/Fence?abc=111 HTTP/1.1 43 | Host: 100.69.238.44:8000 44 | Accept: */* 45 | didi-header-rid: 6 46 | Content-Type: application/x-www-form-urlencoded 47 | 48 | flat=22&feature=0&sign=&version=1.0.0` 49 | 50 | dft := Default() 51 | vec, _ := dft.Lex2Vector([]byte(req)) 52 | fmt.Println(vec) 53 | vec, _ = lexer.Lex2Vector([]byte(req)) 54 | fmt.Println(vec) 55 | } 56 | -------------------------------------------------------------------------------- /koala/replaying/lexer/sapi.go: -------------------------------------------------------------------------------- 1 | package lexer 2 | 3 | var defaultLexer = Default() 4 | var Lexers []ProtocolLexer 5 | 6 | func Lex(text []byte) []string { 7 | for _, lexer := range Lexers { 8 | if !lexer.Match(text) { 9 | continue 10 | } 11 | ret, err := lexer.Lex(text) 12 | if err == nil { 13 | return ret 14 | } 15 | } 16 | ret, _ := defaultLexer.Lex(text) 17 | return ret 18 | } 19 | 20 | func Lex2Vector(text []byte) map[string]float64 { 21 | for _, lexer := range Lexers { 22 | if !lexer.Match(text) { 23 | continue 24 | } 25 | ret, err := lexer.Lex2Vector(text) 26 | if err == nil { 27 | return ret 28 | } 29 | } 30 | ret, _ := defaultLexer.Lex2Vector(text) 31 | return ret 32 | } 33 | -------------------------------------------------------------------------------- /koala/replaying/matcher.go: -------------------------------------------------------------------------------- 1 | package replaying 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "github.com/didi/rdebug/koala/recording" 8 | ) 9 | 10 | const SimMatch = "sim" 11 | 12 | type MatcherIf interface { 13 | Match(connMatchContext *ConnMatchContext, request []byte, replayingSession *ReplayingSession) (int, float64, *recording.CallOutbound) 14 | RShutdown(replayingSession *ReplayingSession) bool // ReplayingSession shutdown 15 | } 16 | 17 | // connect level's matched context 18 | type ConnMatchContext struct { 19 | ClientAddr *net.TCPAddr 20 | LastMatchedIndex int 21 | MatchedCounter map[string]int 22 | } 23 | 24 | func NewConnMatchContext(addr *net.TCPAddr, lastMatchedIndex int) *ConnMatchContext { 25 | return &ConnMatchContext{ 26 | ClientAddr: addr, 27 | LastMatchedIndex: lastMatchedIndex, 28 | MatchedCounter: map[string]int{}, 29 | } 30 | } 31 | 32 | func (c *ConnMatchContext) UpdateCounter(callOutbound *recording.CallOutbound) { 33 | key := callOutbound.GetIdentifier() 34 | if len(key) == 0 { 35 | return 36 | } 37 | counter := c.MatchedCounter 38 | if count, ok := counter[key]; ok { 39 | counter[key] = count + 1 40 | } else { 41 | counter[key] = 1 42 | } 43 | c.MatchedCounter = counter 44 | } 45 | 46 | func (c *ConnMatchContext) String () string { 47 | return fmt.Sprintf("{ClientAddr=%s, LastMatchedIndex=%d, MatchedCounter=%#v}", 48 | c.ClientAddr.String(), c.LastMatchedIndex, c.MatchedCounter) 49 | } 50 | -------------------------------------------------------------------------------- /koala/replaying/matcher_cosine_test.go: -------------------------------------------------------------------------------- 1 | package replaying 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func Test_getMatchedIndexOfMultiMaxScore_one_connect_multi_matched(t *testing.T) { 10 | connCtx := NewConnMatchContext(nil, 21) 11 | connCtx.MatchedCounter = map[string]int{"100.90.104.35:3000#65":1} 12 | maxScoreIdxs := map[int]string { 13 | 21: "100.90.104.35:3000#65", 14 | 23: "100.90.104.35:3000#65", 15 | 55: "100.90.104.35:3000#65", 16 | } 17 | should := require.New(t) 18 | should.Equal(22, getMatchedIndexOfMultiMaxScore(connCtx, maxScoreIdxs, 22, 21)) 19 | } 20 | 21 | func Test_getMatchedIndexOfMultiMaxScore_one_connect_multi_counter_multi_matched(t *testing.T) { 22 | connCtx := NewConnMatchContext(nil, -1) 23 | connCtx.MatchedCounter = map[string]int{"100.90.233.21:3000#60":1, "100.90.103.16:3000#67":1} 24 | maxScoreIdxs := map[int]string { 25 | 43: "100.90.233.21:3000#60", 26 | 48: "100.90.103.16:3000#67", 27 | } 28 | should := require.New(t) 29 | should.Equal(48, getMatchedIndexOfMultiMaxScore(connCtx, maxScoreIdxs, 47, 43)) 30 | } 31 | 32 | func Test_getMatchedIndexOfMultiMaxScore_one_connect_multi_counter_multi_matched_2(t *testing.T) { 33 | connCtx := NewConnMatchContext(nil, 14) 34 | connCtx.MatchedCounter = map[string]int{"100.70.148.57:3000#60":4} 35 | maxScoreIdxs := map[int]string { 36 | 18: "100.70.148.57:3000#60", 37 | 67: "100.70.148.57:3000#60", 38 | } 39 | should := require.New(t) 40 | should.Equal(18, getMatchedIndexOfMultiMaxScore(connCtx, maxScoreIdxs, 17, 18)) 41 | } 42 | 43 | func Test_getMatchedIndexOfMultiMaxScore_new_connect_multi_matched(t *testing.T) { 44 | connCtx := NewConnMatchContext(nil, -1) 45 | maxScoreIdxs := map[int]string { 46 | 43: "100.90.233.21:3000#60", 47 | 48: "100.90.103.16:3000#67", 48 | } 49 | should := require.New(t) 50 | should.Equal(48, getMatchedIndexOfMultiMaxScore(connCtx, maxScoreIdxs, 47, 43)) 51 | } 52 | 53 | func Test_getMatchedIndexOfMultiMaxScore_new_connect_multi_matched_2(t *testing.T) { 54 | connCtx := NewConnMatchContext(nil, -1) 55 | maxScoreIdxs := map[int]string { 56 | 5: "100.90.233.21:3000#60", 57 | 0: "100.90.103.16:3000#67", 58 | } 59 | should := require.New(t) 60 | should.Equal(0, getMatchedIndexOfMultiMaxScore(connCtx, maxScoreIdxs, -1, 0)) 61 | } 62 | -------------------------------------------------------------------------------- /koala/replaying/replayed.go: -------------------------------------------------------------------------------- 1 | package replaying 2 | 3 | type ReplayedSession struct { 4 | SessionId string 5 | CallFromInbound *CallFromInbound 6 | ReturnInbound *ReturnInbound 7 | Actions []ReplayedAction 8 | } 9 | -------------------------------------------------------------------------------- /koala/replaying/replaying_match.go: -------------------------------------------------------------------------------- 1 | package replaying 2 | 3 | import ( 4 | "github.com/didi/rdebug/koala/envarg" 5 | ) 6 | 7 | var Matcher MatcherIf 8 | 9 | func init() { 10 | if envarg.ReplayingMatchStrategy() == SimMatch { 11 | threshold := envarg.ReplayingMatchThreshold() 12 | Matcher = &CosineMatcher{Threshold: threshold} 13 | } else { 14 | Matcher = &ChunkMatcher{} 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /koala/replaying/tmp.go: -------------------------------------------------------------------------------- 1 | package replaying 2 | 3 | import ( 4 | "net" 5 | "sync" 6 | "github.com/v2pro/plz/countlog" 7 | ) 8 | 9 | var tmp = map[string]*ReplayingSession{} 10 | var tmpMutex = &sync.Mutex{} 11 | 12 | func StoreTmp(inboundAddr net.TCPAddr, session *ReplayingSession) { 13 | tmpMutex.Lock() 14 | defer tmpMutex.Unlock() 15 | tmp[inboundAddr.String()] = session 16 | } 17 | 18 | func RetrieveTmp(inboundAddr net.TCPAddr) *ReplayingSession { 19 | tmpMutex.Lock() 20 | defer tmpMutex.Unlock() 21 | key := inboundAddr.String() 22 | session := tmp[key] 23 | delete(tmp, key) 24 | return session 25 | } 26 | 27 | func AssignLocalAddr() (*net.TCPAddr, error) { 28 | // golang does not provide api to bind before connect 29 | // this is a hack to assign 127.0.0.1:0 to pre-determine a local port 30 | listener, err := net.Listen("tcp", "127.0.0.1:0") // ask for new port 31 | if err != nil { 32 | countlog.Error("event!replaying.failed to resolve local tcp addr port", "err", err) 33 | return nil, err 34 | } 35 | localAddr := listener.Addr().(*net.TCPAddr) 36 | err = listener.Close() 37 | if err != nil { 38 | countlog.Error("event!replaying.failed to close", "err", err) 39 | return nil, err 40 | } 41 | return localAddr, nil 42 | } 43 | -------------------------------------------------------------------------------- /koala/sut/helper.go: -------------------------------------------------------------------------------- 1 | package sut 2 | 3 | import ( 4 | "github.com/v2pro/plz/countlog" 5 | "bytes" 6 | ) 7 | 8 | var helperThreadShutdown = "to-koala!thread-shutdown" 9 | var helperCallFunction = "to-koala!call-function" 10 | var helperReturnFunction = "to-koala!return-function" 11 | var helperReadStorage = "to-koala!read-storage" 12 | var helperSetDelegatedFromThreadId = "to-koala!set-delegated-from-thread-id" 13 | var helperGetTraceHeader = "to-koala!get-trace-header" 14 | var helperGetTraceHeaderKey = "to-koala!get-trace-header-key" 15 | var helperSetTraceHeaderKey = "to-koala!set-trace-header-key" 16 | 17 | func SendToKoala(threadID ThreadID, span []byte, flags SendToFlags) { 18 | helperInfo := span 19 | countlog.Trace("event!sut.send_to_koala", 20 | "threadID", threadID, 21 | "flags", flags, 22 | "content", helperInfo) 23 | newlinePos := bytes.IndexByte(helperInfo, '\n') 24 | if newlinePos == -1 { 25 | return 26 | } 27 | body := helperInfo[newlinePos+1:] 28 | switch string(helperInfo[:newlinePos]) { 29 | case helperThreadShutdown: 30 | if flags != 0 { 31 | operateVirtualThread(ThreadID(flags), func(thread *Thread) { 32 | thread.OnShutdown() 33 | }) 34 | } else { 35 | OperateThread(threadID, func(thread *Thread) { 36 | thread.OnShutdown() 37 | }) 38 | } 39 | case helperCallFunction: 40 | OperateThread(threadID, func(thread *Thread) { 41 | thread.replayingSession.CallFunction(thread, body) 42 | }) 43 | case helperReturnFunction: 44 | OperateThread(threadID, func(thread *Thread) { 45 | thread.replayingSession.ReturnFunction(thread, body) 46 | }) 47 | case helperReadStorage: 48 | OperateThread(threadID, func(thread *Thread) { 49 | thread.recordingSession.ReadStorage(thread, body) 50 | }) 51 | case helperSetDelegatedFromThreadId: 52 | realThreadId := threadID 53 | virtualThreadId := ThreadID(flags) 54 | mapThreadRelation(realThreadId, virtualThreadId) 55 | case helperGetTraceHeader: 56 | OperateThread(threadID, func(thread *Thread) { 57 | if thread.recordingSession != nil { 58 | thread.helperResponse = thread.recordingSession.GetTraceHeader() 59 | } 60 | }) 61 | case helperGetTraceHeaderKey: 62 | OperateThread(threadID, func(thread *Thread) { 63 | if thread.recordingSession != nil { 64 | key := body 65 | thread.helperResponse = thread.recordingSession.GetTraceHeader().Get(key) 66 | } 67 | }) 68 | case helperSetTraceHeaderKey: 69 | OperateThread(threadID, func(thread *Thread) { 70 | if thread.recordingSession != nil { 71 | newlinePos = bytes.IndexByte(body, '\n') 72 | if newlinePos == -1 { 73 | countlog.Error("event!sut.SetTraceHeaderKey expects newline as separator", 74 | "body", body) 75 | return 76 | } 77 | key := body[:newlinePos] 78 | value := body[newlinePos+1:] 79 | thread.recordingSession.TraceHeader = thread.recordingSession.GetTraceHeader().Set(key, value) 80 | } 81 | }) 82 | default: 83 | countlog.Debug("event!sut.unknown_helper", 84 | "threadID", threadID, 85 | "helperType", string(helperInfo[:newlinePos])) 86 | } 87 | } 88 | 89 | func RecvFromKoala(threadID ThreadID) []byte { 90 | thread := getThread(threadID) 91 | response := thread.helperResponse 92 | thread.helperResponse = nil 93 | countlog.Trace("event!sut.recv_from_koala", 94 | "threadID", threadID, 95 | "response", response) 96 | return response 97 | } 98 | -------------------------------------------------------------------------------- /koala/sut/mock_file.go: -------------------------------------------------------------------------------- 1 | package sut 2 | 3 | import ( 4 | "crypto/sha1" 5 | "encoding/base32" 6 | "io/ioutil" 7 | "github.com/v2pro/plz/countlog" 8 | "os" 9 | "github.com/didi/rdebug/koala/envarg" 10 | "sync" 11 | ) 12 | 13 | var mockedFiles = map[string]bool{} 14 | var mockedFilesMutex = &sync.Mutex{} 15 | 16 | func init() { 17 | if envarg.IsReplaying() { 18 | if _, err := os.Stat("/tmp/koala-mocked-files"); err != nil { 19 | // dir not created yet, create 20 | err = os.Mkdir("/tmp/koala-mocked-files", 0777) 21 | countlog.Error("event!sut.failed to create mocked dir", "err", err) 22 | } 23 | } 24 | } 25 | 26 | func mockFile(content []byte) string { 27 | mockedFile := "/tmp/koala-mocked-files/" + hash(content) 28 | if isMocked(mockedFile) { 29 | return mockedFile 30 | } 31 | setMocked(mockedFile) 32 | if _, err := os.Stat(mockedFile); err == nil { 33 | return mockedFile 34 | } 35 | err := ioutil.WriteFile(mockedFile + ".tmp", content, 0666) 36 | if err != nil { 37 | countlog.Error("event!sut.failed to write mock file", 38 | "mockedFile", mockedFile, "err", err) 39 | return "" 40 | } 41 | err = os.Rename(mockedFile + ".tmp", mockedFile) 42 | if err != nil { 43 | countlog.Error("event!sut.failed to rename mock file tmp", 44 | "mockedFile", mockedFile, "err", err) 45 | return "" 46 | } 47 | return mockedFile 48 | } 49 | 50 | func hash(content []byte) string { 51 | h := sha1.New() 52 | h.Write(content) 53 | return "g" + base32.StdEncoding.EncodeToString(h.Sum(nil)) 54 | } 55 | 56 | func isMocked(mockedFile string) bool { 57 | mockedFilesMutex.Lock() 58 | defer mockedFilesMutex.Unlock() 59 | return mockedFiles[mockedFile] 60 | } 61 | 62 | func setMocked(mockedFile string) { 63 | mockedFilesMutex.Lock() 64 | defer mockedFilesMutex.Unlock() 65 | mockedFiles[mockedFile] = true 66 | } 67 | -------------------------------------------------------------------------------- /koala/sut/socket_fd.go: -------------------------------------------------------------------------------- 1 | package sut 2 | 3 | import ( 4 | "net" 5 | "syscall" 6 | "github.com/v2pro/plz/countlog" 7 | "time" 8 | ) 9 | 10 | func bindFDToLocalAddr(socketFD int) (*net.TCPAddr, error) { 11 | localAddr, err := syscall.Getsockname(int(socketFD)) 12 | if err != nil { 13 | return nil, err 14 | } 15 | localInet4Addr := localAddr.(*syscall.SockaddrInet4) 16 | if localInet4Addr.Port != 0 && localInet4Addr.Addr != [4]byte{} { 17 | return &net.TCPAddr{ 18 | IP: localInet4Addr.Addr[:], 19 | Port: localInet4Addr.Port, 20 | }, nil 21 | } 22 | err = syscall.Bind(socketFD, &syscall.SockaddrInet4{ 23 | Addr: [4]byte{127, 0, 0, 1}, 24 | Port: 0, 25 | }) 26 | if err != nil { 27 | return nil, err 28 | } 29 | localAddr, err = syscall.Getsockname(int(socketFD)) 30 | if err != nil { 31 | return nil, err 32 | } 33 | localInet4Addr = localAddr.(*syscall.SockaddrInet4) 34 | return &net.TCPAddr{ 35 | IP: localInet4Addr.Addr[:], 36 | Port: localInet4Addr.Port, 37 | }, nil 38 | } 39 | 40 | func (thread *Thread) lookupSocket(socketFD SocketFD) *socket { 41 | sock := thread.socks[socketFD] 42 | if sock != nil { 43 | return sock 44 | } 45 | sock = getGlobalSock(socketFD) 46 | if sock == nil { 47 | return nil 48 | } 49 | remoteAddr, err := syscall.Getpeername(int(socketFD)) 50 | if err != nil { 51 | countlog.Error("event!failed to get peer name", "err", err, "socketFD", socketFD) 52 | return nil 53 | } 54 | remoteAddr4, _ := remoteAddr.(*syscall.SockaddrInet4) 55 | // if remote address changed, the fd must be closed and reused 56 | if remoteAddr4 != nil && (remoteAddr4.Port != sock.addr.Port || 57 | remoteAddr4.Addr[0] != sock.addr.IP[0] || 58 | remoteAddr4.Addr[1] != sock.addr.IP[1] || 59 | remoteAddr4.Addr[2] != sock.addr.IP[2] || 60 | remoteAddr4.Addr[3] != sock.addr.IP[3]) { 61 | sock = &socket{ 62 | socketFD: socketFD, 63 | isServer: false, 64 | addr: net.TCPAddr{ 65 | Port: remoteAddr4.Port, 66 | IP: net.IP(remoteAddr4.Addr[:]), 67 | }, 68 | lastAccessedAt: time.Now(), 69 | } 70 | setGlobalSock(socketFD, sock) 71 | } 72 | remoteAddr6, _ := remoteAddr.(*syscall.SockaddrInet6) 73 | if remoteAddr6 != nil && (remoteAddr6.Port != sock.addr.Port || 74 | remoteAddr6.Addr[0] != sock.addr.IP[0] || 75 | remoteAddr6.Addr[1] != sock.addr.IP[1] || 76 | remoteAddr6.Addr[2] != sock.addr.IP[2] || 77 | remoteAddr6.Addr[3] != sock.addr.IP[3] || 78 | remoteAddr6.Addr[4] != sock.addr.IP[4] || 79 | remoteAddr6.Addr[5] != sock.addr.IP[5]) { 80 | sock = &socket{ 81 | socketFD: socketFD, 82 | isServer: false, 83 | addr: net.TCPAddr{ 84 | Port: remoteAddr6.Port, 85 | IP: net.IP(remoteAddr6.Addr[:]), 86 | }, 87 | lastAccessedAt: time.Now(), 88 | } 89 | setGlobalSock(socketFD, sock) 90 | } 91 | thread.socks[socketFD] = sock 92 | return sock 93 | } 94 | -------------------------------------------------------------------------------- /koala/sut/thread_test.go: -------------------------------------------------------------------------------- 1 | package sut 2 | 3 | import ( 4 | "testing" 5 | "encoding/binary" 6 | "fmt" 7 | ) 8 | 9 | func Test_127_127_127_127(t *testing.T) { 10 | fmt.Println(binary.BigEndian.Uint32([]byte{127,127,127,127})) 11 | } 12 | -------------------------------------------------------------------------------- /koala/sut/time_hook.go: -------------------------------------------------------------------------------- 1 | package sut 2 | 3 | var SetTimeOffset = func(offset int) { 4 | } 5 | -------------------------------------------------------------------------------- /koala/test/.gitignore: -------------------------------------------------------------------------------- 1 | /bin 2 | -------------------------------------------------------------------------------- /koala/test/java/.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | -------------------------------------------------------------------------------- /koala/test/java/Server.java: -------------------------------------------------------------------------------- 1 | import com.sun.net.httpserver.HttpExchange; 2 | import com.sun.net.httpserver.HttpHandler; 3 | import com.sun.net.httpserver.HttpServer; 4 | 5 | import java.io.IOException; 6 | import java.io.OutputStream; 7 | import java.net.InetSocketAddress; 8 | import java.util.Date; 9 | 10 | public class Server { 11 | public static void main(String[] args) throws Exception { 12 | HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 2515), 0); 13 | server.createContext("/", new MyHandler()); 14 | server.setExecutor(null); // creates a default executor 15 | server.start(); 16 | } 17 | 18 | static class MyHandler implements HttpHandler { 19 | @Override 20 | public void handle(HttpExchange t) throws IOException { 21 | String response = "This is the response " + new Date().toString(); 22 | t.sendResponseHeaders(200, response.length()); 23 | OutputStream os = t.getResponseBody(); 24 | os.write(response.getBytes()); 25 | os.close(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /koala/test/server.py: -------------------------------------------------------------------------------- 1 | import BaseHTTPServer 2 | import SocketServer 3 | import socket 4 | import threading 5 | import requests 6 | import os 7 | import datetime 8 | 9 | PORT = 2515 10 | 11 | logFile = open('/tmp/server.log', 'a') 12 | 13 | sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 14 | 15 | class MyHandler(BaseHTTPServer.BaseHTTPRequestHandler): 16 | def do_GET(self): 17 | if self.path == '/': 18 | sock.sendto('to-koala!set-trace-header-key\norder_id\nworld', 0, ('127.127.127.127', 127)) 19 | s = requests.Session() 20 | s.get('http://127.0.0.1:2515/branch') 21 | self.send_response(200) 22 | self.wfile.write(str(datetime.datetime.now())) 23 | if self.path == '/branch': 24 | sock.sendto('to-koala!get-trace-header\n', 0, ('127.127.127.127', 127)) 25 | trace_header = sock.recvfrom(4096, 127127)[0] 26 | s = requests.Session() 27 | s.get('http://127.0.0.1:2515/leaf1') 28 | s.get('http://127.0.0.1:2515/leaf2') 29 | self.send_response(200) 30 | self.wfile.write(trace_header) 31 | elif self.path == '/leaf1': 32 | sock.sendto('to-koala!get-trace-header-key\norder_id', 0, ('127.127.127.127', 127)) 33 | order_id = sock.recvfrom(4096, 127127)[0] 34 | self.send_response(200) 35 | self.wfile.write(order_id) 36 | elif self.path == '/leaf2': 37 | self.send_response(200) 38 | self.wfile.write('leaf2') 39 | 40 | 41 | class ThreadingMixIn: 42 | """Mix-in class to handle each request in a new thread.""" 43 | 44 | # Decides how threads will act upon termination of the 45 | # main process 46 | daemon_threads = False 47 | 48 | def process_request_thread(self, request, client_address): 49 | """Same as in BaseServer but as a thread. 50 | 51 | In addition, exception handling is done here. 52 | 53 | """ 54 | try: 55 | try: 56 | self.finish_request(request, client_address) 57 | self.shutdown_request(request) 58 | except: 59 | self.handle_error(request, client_address) 60 | self.shutdown_request(request) 61 | finally: 62 | sock.sendto( 63 | 'to-koala!thread-shutdown\n', 64 | ('127.127.127.127', 127)) 65 | 66 | def process_request(self, request, client_address): 67 | """Start a new thread to process the request.""" 68 | t = threading.Thread(target=self.process_request_thread, 69 | args=(request, client_address)) 70 | t.daemon = self.daemon_threads 71 | t.start() 72 | 73 | 74 | class ThreadedTCPServer(ThreadingMixIn, SocketServer.TCPServer): 75 | pass 76 | 77 | 78 | SocketServer.TCPServer.allow_reuse_address = True 79 | if os.getenv('SERVER_MODE') == 'MULTI_THREADS': 80 | httpd = ThreadedTCPServer(("", PORT), MyHandler) 81 | else: 82 | httpd = SocketServer.TCPServer(("", PORT), MyHandler) 83 | 84 | print "serving at port", PORT 85 | httpd.serve_forever() 86 | -------------------------------------------------------------------------------- /koala/test/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "github.com/didi/rdebug/koala" 6 | "github.com/v2pro/plz/countlog" 7 | ) 8 | 9 | func main() { 10 | http.HandleFunc("/", func(respWriter http.ResponseWriter, req *http.Request) { 11 | countlog.Info("event!test_server.enter_handler", 12 | "threadID", koala.GetCurrentGoRoutineId(), 13 | "url", req.URL.String()) 14 | _, err := http.Get("http://127.0.0.1:1/not-exist") 15 | if err != nil { 16 | respWriter.Write([]byte(err.Error())) 17 | return 18 | } 19 | respWriter.Write([]byte("good day")) 20 | }) 21 | http.ListenAndServe("127.0.0.1:2515", nil) 22 | } 23 | -------------------------------------------------------------------------------- /output/bin/midi-diplugin.phar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/output/bin/midi-diplugin.phar -------------------------------------------------------------------------------- /output/bin/midi.phar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/output/bin/midi.phar -------------------------------------------------------------------------------- /output/libs/koala-libc.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/output/libs/koala-libc.so -------------------------------------------------------------------------------- /output/libs/koala-recorder.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/output/libs/koala-recorder.so -------------------------------------------------------------------------------- /output/libs/koala-replayer.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/output/libs/koala-replayer.so -------------------------------------------------------------------------------- /php/midi/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | vendor 4 | *.swp 5 | tags 6 | cscope.out 7 | *.DS_Store 8 | -------------------------------------------------------------------------------- /php/midi/CHANGELOG.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/php/midi/CHANGELOG.md -------------------------------------------------------------------------------- /php/midi/bin/midi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | add(new Midi\Command\UpdateCommand()); 9 | } 10 | $app->run(); 11 | -------------------------------------------------------------------------------- /php/midi/box-diplugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "chmod": "0755", 3 | "directories": [ 4 | "src", 5 | "res" 6 | ], 7 | "files": ["version.txt", "version-dev.txt", "LICENSE", "CHANGELOG.md", "README.md"], 8 | "finder": [ 9 | { 10 | "name": "*.php", 11 | "notPath": ["#.*/Tests#", "#.*/tests/#", "#.*/test/#"], 12 | "in": [ 13 | "vendor" 14 | ] 15 | }, 16 | { 17 | "in": [ 18 | "vendor/phpunit/php-code-coverage/src/Report/Html" 19 | ] 20 | } 21 | ], 22 | "main": "bin/midi", 23 | "output": "midi-diplugin.phar", 24 | "stub": true, 25 | "compactors": [ 26 | "Herrera\\Box\\Compactor\\Json", 27 | "Herrera\\Box\\Compactor\\Php" 28 | ], 29 | "compression": "BZ2" 30 | } 31 | -------------------------------------------------------------------------------- /php/midi/box.json: -------------------------------------------------------------------------------- 1 | { 2 | "chmod": "0755", 3 | "directories": [ 4 | "src/Midi", 5 | "res" 6 | ], 7 | "files": ["version.txt", "version-dev.txt", "LICENSE", "CHANGELOG.md", "README.md", "src/bootstrap.php"], 8 | "finder": [ 9 | { 10 | "name": "*.php", 11 | "notPath": ["#.*/Tests#", "#.*/tests/#", "#.*/test/#"], 12 | "in": [ 13 | "vendor" 14 | ] 15 | }, 16 | { 17 | "in": [ 18 | "vendor/phpunit/php-code-coverage/src/Report/Html" 19 | ] 20 | } 21 | ], 22 | "main": "bin/midi", 23 | "output": "midi.phar", 24 | "stub": true, 25 | "compactors": [ 26 | "Herrera\\Box\\Compactor\\Json", 27 | "Herrera\\Box\\Compactor\\Php" 28 | ], 29 | "compression": "BZ2" 30 | } 31 | -------------------------------------------------------------------------------- /php/midi/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Build midi.phar 4 | 5 | set -e 6 | set -x 7 | 8 | # Install box for phar 9 | # composer global require kherge/box --prefer-source 10 | 11 | # Install depends 12 | source ./install.sh 13 | 14 | config='box.json' 15 | output='midi.phar' 16 | 17 | case $1 in 18 | "midi-diplugin" ) 19 | config='box-diplugin.json' 20 | output='midi-diplugin.phar' 21 | ;; 22 | esac 23 | 24 | ~/.composer/vendor/bin/box build -c $config -v 25 | mv $output ../../output/bin/$output 26 | 27 | ## clean 28 | rm -rf vendor 29 | rm composer.json composer.lock -------------------------------------------------------------------------------- /php/midi/doc/DiPlugin/midi-didi-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/php/midi/doc/DiPlugin/midi-didi-diff.png -------------------------------------------------------------------------------- /php/midi/doc/DiPlugin/midi-didi-log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/php/midi/doc/DiPlugin/midi-didi-log.png -------------------------------------------------------------------------------- /php/midi/doc/DiPlugin/midi-didi-upstream.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/php/midi/doc/DiPlugin/midi-didi-upstream.png -------------------------------------------------------------------------------- /php/midi/doc/xdebug/Breakpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/php/midi/doc/xdebug/Breakpoint.png -------------------------------------------------------------------------------- /php/midi/doc/xdebug/DBGp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/php/midi/doc/xdebug/DBGp.png -------------------------------------------------------------------------------- /php/midi/doc/xdebug/Debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/php/midi/doc/xdebug/Debug.png -------------------------------------------------------------------------------- /php/midi/doc/xdebug/Listen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/php/midi/doc/xdebug/Listen.png -------------------------------------------------------------------------------- /php/midi/doc/xdebug/Servers-Index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/php/midi/doc/xdebug/Servers-Index.png -------------------------------------------------------------------------------- /php/midi/doc/xdebug/Servers-nuwa-mapping.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/php/midi/doc/xdebug/Servers-nuwa-mapping.png -------------------------------------------------------------------------------- /php/midi/doc/xdebug/Servers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/php/midi/doc/xdebug/Servers.png -------------------------------------------------------------------------------- /php/midi/doc/xdebug/Xdebug-Show.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/php/midi/doc/xdebug/Xdebug-Show.png -------------------------------------------------------------------------------- /php/midi/doc/xdebug/Xdebug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/php/midi/doc/xdebug/Xdebug.png -------------------------------------------------------------------------------- /php/midi/doc/xdebug/prework.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/php/midi/doc/xdebug/prework.png -------------------------------------------------------------------------------- /php/midi/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # when git clone source to local and use this script to install depends 4 | # Install from source, not by composer 5 | 6 | set -e 7 | set -x 8 | 9 | # composer.json must at root directory 10 | # copy composer.json to current directory, change autoload path to current dir 11 | if [ ! -f "composer.json" ]; then 12 | cp ../../composer.json . 13 | fi 14 | if [[ "$OSTYPE" == "linux-gnu" ]]; then 15 | sed -i -e "s#php/midi/src#src#g" composer.json 16 | sed -i -e "s#php/midi/tests#tests#g" composer.json 17 | sed -i -e "s#php/midi/bin#bin#g" composer.json 18 | sed -i -e "s#php/midi/res#res#g" composer.json 19 | elif [[ "$OSTYPE" == "darwin"* ]]; then 20 | sed -i '' -e "s#php/midi/src#src#g" composer.json 21 | sed -i '' -e "s#php/midi/tests#tests#g" composer.json 22 | sed -i '' -e "s#php/midi/bin#bin#g" composer.json 23 | sed -i '' -e "s#php/midi/res#res#g" composer.json 24 | else 25 | echo "Not Support $OSTYPE" 26 | exit 1 27 | fi 28 | 29 | # composer install depends 30 | if [ -d "vendor" ]; then 31 | rm -rf vendor 32 | fi 33 | composer install -o 34 | -------------------------------------------------------------------------------- /php/midi/phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | tests/Midi 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /php/midi/res/diplugin/README.md: -------------------------------------------------------------------------------- 1 | ## Res for DiPlugin 2 | 3 | DiPlugin 插件涉及到内部的一些数据和配置,此目录进行脱敏处理。配置为空 或 保留简单示例。 -------------------------------------------------------------------------------- /php/midi/res/diplugin/coverage/example.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /home/xiaoju/webroot/gulfstream/application/carpool/v1/index.php 5 | ci 6 | 7 | 8 | 9 | /home/xiaoju/webroot/gulfstream/application/carpool/v1/controllers 10 | /home/xiaoju/webroot/gulfstream/application/carpool/v1/models 11 | /home/xiaoju/webroot/gulfstream/application/carpool/v1/logics 12 | 13 | /home/xiaoju/webroot/gulfstream/application/carpool/v1/controllers/test 14 | /home/xiaoju/webroot/gulfstream/application/carpool/v1/controllers/welcome.php 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /php/midi/res/diplugin/disf/naming.json: -------------------------------------------------------------------------------- 1 | { 2 | "services": {}, 3 | "ports": {} 4 | } 5 | -------------------------------------------------------------------------------- /php/midi/res/diplugin/disf/services.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /php/midi/res/replayer/koala-replayer.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/rdebug/3a4b9dcb67951d0197a16dd3763157e8c3412d35/php/midi/res/replayer/koala-replayer.so -------------------------------------------------------------------------------- /php/midi/res/template/report/index.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Midi Replayed Report 8 | 9 | 10 |
11 | {{ include('replayed-alter.twig') }} 12 | {{ include('replayed-session-list.twig') }} 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /php/midi/res/template/report/replayed-alter.twig: -------------------------------------------------------------------------------- 1 | {% if replayedAlter.all_success > 0 %} 2 | 10 | {% else %} 11 | 19 | {% endif %} 20 | 21 | 24 | 27 | 28 | -------------------------------------------------------------------------------- /php/midi/res/template/report/replayed-session-list.twig: -------------------------------------------------------------------------------- 1 |
2 | {% if replayedSessions.sessions|length == 1 %} 3 | {% set expanded = "true" %} 4 | {% set show = "show" %} 5 | {% else %} 6 | {% set expanded = "false" %} 7 | {% set show = "" %} 8 | {% endif %} 9 | {% for index, session in replayedSessions.sessions %} 10 |
11 | 12 | {% if session.Same == 1 %} 13 | {% set session_color = "success" %} 14 | {% else %} 15 | {% set session_color = "danger" %} 16 | {% endif %} 17 | #{{ index+1 }} {{ session.Request[:70] }} 20 | 21 | 22 |
23 |
24 | 54 |
55 |
56 |
57 | {% endfor %} 58 |
-------------------------------------------------------------------------------- /php/midi/res/template/report/replayed-tab-coverage.twig: -------------------------------------------------------------------------------- 1 | {% if firstTab == 1 %} 2 | {% set tabActive = "active" %} 3 | {% else %} 4 | {% set tabActive = "" %} 5 | {% endif %} 6 | 7 | 18 | -------------------------------------------------------------------------------- /php/midi/res/template/report/replayed-tab-log.twig: -------------------------------------------------------------------------------- 1 | {% if firstTab == 1 %} 2 | {% set tabActive = "active" %} 3 | {% else %} 4 | {% set tabActive = "" %} 5 | {% endif %} 6 | 7 | 50 | -------------------------------------------------------------------------------- /php/midi/res/template/report/replayed-tab-request.twig: -------------------------------------------------------------------------------- 1 | {% if firstTab == 1 %} 2 | {% set tabActive = "active" %} 3 | {% else %} 4 | {% set tabActive = "" %} 5 | {% endif %} 6 | 7 | 54 | -------------------------------------------------------------------------------- /php/midi/res/template/report/replayed-tab-trace.twig: -------------------------------------------------------------------------------- 1 | {% if firstTab == 1 %} 2 | {% set tabActive = "active" %} 3 | {% else %} 4 | {% set tabActive = "" %} 5 | {% endif %} 6 | 7 | 18 | -------------------------------------------------------------------------------- /php/midi/res/template/report/replayed-trace.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Midi: Xdebug Trace Report 6 | 17 | 18 | 23 | 83 | 84 | 85 |
    86 |
  • 87 | {{ SessionId }} 88 |
      89 | {{ TraceList|raw }} 90 |
    91 |
  • 92 |
93 | 94 | 95 | -------------------------------------------------------------------------------- /php/midi/res/template/report/static/collapsible/CollapsibleLists.js: -------------------------------------------------------------------------------- 1 | //code.iamkate.com 2 | var CollapsibleLists=function(){function e(b,c){[].forEach.call(b.getElementsByTagName("li"),function(a){c&&b!==a.parentNode||(a.style.userSelect="none",a.style.MozUserSelect="none",a.style.msUserSelect="none",a.style.WebkitUserSelect="none",a.addEventListener("click",g.bind(null,a)),f(a))})}function g(b,c){for(var a=c.target;"LI"!==a.nodeName;)a=a.parentNode;a===b&&f(b)}function f(b){var c=b.classList.contains("collapsibleListClosed"),a=b.getElementsByTagName("ul");[].forEach.call(a,function(a){for(var d=a;"LI"!==d.nodeName;)d=d.parentNode;d===b&&(a.style.display=c?"block":"none")});b.classList.remove("collapsibleListOpen");b.classList.remove("collapsibleListClosed");0setName('doctor') 22 | ->setDescription('doctor local environment and depends') 23 | ->setHelp('php midi doctor'); 24 | } 25 | 26 | protected function execute(InputInterface $input, OutputInterface $output) 27 | { 28 | try { 29 | $output->writeln(Message::DOCTOR_COMMAND_WELCOME_INFO); 30 | ReplayerCommand::prepareReplayerSo(true); 31 | PreKoalaCheck::checkPhpExt(); 32 | 33 | Util::throwIfPortsUsed($this->getMidi()->getKoala()->getPorts()); 34 | 35 | if ($moduleName = DiConfig::getModuleName()) { 36 | // under module's dir 37 | PreKoalaCheck::checkProjectDir(); 38 | PreKoalaCheck::checkBizConfig(); 39 | PreKoalaCheck::prepareCISystem(true); 40 | } else { 41 | $output->writeln("You should also `midi doctor` in your project dir."); 42 | } 43 | 44 | if (OS::isMacOs()) { 45 | Util::checkMacOSVersion(); 46 | } 47 | } catch (Exception $e) { 48 | $output->writeln($e->getMessage()); 49 | $output->writeln("" . Message::DOCTOR_COMMAND_MIDI_WIKI . ""); 50 | return; 51 | } 52 | 53 | $output->writeln("Everything is OK. Just enjoy it!"); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /php/midi/src/DiPlugin/Command/InitCommand.php: -------------------------------------------------------------------------------- 1 | setName('init') 24 | ->setDescription('Initialize midi, mock disf...') 25 | ->addOption('--module', '-m', InputOption::VALUE_OPTIONAL, "Module name") 26 | ->addOption('--increase', '-i', InputOption::VALUE_NONE, "Increase mode") 27 | ->setHelp('php midi init'); 28 | } 29 | 30 | protected function execute(InputInterface $input, OutputInterface $output) 31 | { 32 | try { 33 | $workingDir = Container::make('workingDir'); 34 | $module = $input->getOption('module'); 35 | $increase = $input->getOption('increase'); 36 | if ($increase) { 37 | $output->writeln("Use increase mode", OutputInterface::VERBOSITY_VERY_VERBOSE); 38 | } 39 | 40 | /** @var Stopwatch $stopWatch */ 41 | $stopWatch = Container::make('stopWatch'); 42 | $stopWatch->start('init'); 43 | 44 | /** @var Application $app */ 45 | $app = $this->getApplication(); 46 | MockDisf::generate($app->getMidi()->getConfig(), $workingDir, $module, $increase); 47 | 48 | $output->writeln(sprintf("Disf generate spent %d ms", 49 | $stopWatch->stop('init')->getDuration()), OutputInterface::VERBOSITY_VERBOSE); 50 | } catch (\Exception $e) { 51 | $output->writeln("{$e->getMessage()}"); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /php/midi/src/DiPlugin/ElasticPlugin.php: -------------------------------------------------------------------------------- 1 | 'onCommandConfigure', 30 | ); 31 | } 32 | 33 | public static function onCommandConfigure(CommandConfigureEvent $event) 34 | { 35 | ElasticResolver::onCommandConfigure($event); 36 | } 37 | } -------------------------------------------------------------------------------- /php/midi/src/DiPlugin/Inject.php: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /php/midi/src/DiPlugin/Message.php: -------------------------------------------------------------------------------- 1 | Require uri or sessionId or file or --all option, get usage `midi help run`.'; 8 | 9 | // search command 10 | const SEARCH_COMMAND_NO_SESSION = 'Search NO sessions'; 11 | 12 | const SEARCH_COUNT_AND_SPENT = 'Search %d Sessions, Spent %d ms'; 13 | 14 | // doctor command 15 | const DOCTOR_COMMAND_WELCOME_INFO = << 17 | 正在使用 doctor 命令检查环境: 18 | 19 | 1. 检查系统,只支持 macOS 20 | 2. 检查 PHP 扩展(Apcu、redis、Memcached),如项目中未使用,可忽略 21 | 3. 检查端口 5514、5515、5516 是否被占用 22 | 4. 如在业务模块下执行 doctor,检查 composer install 等 23 | 5. 检查是否有 biz-config 代码,目录和业务模块目录同级,如项目中未使用,可忽略 24 | 6. 检查系统版本号,当系统版本 > 10.13 时,需要 SIP 关闭 25 | 26 | 27 | EOT; 28 | // TODO PATCH internal wiki url. 29 | const DOCTOR_COMMAND_MIDI_WIKI = 'Get Help See Internal Wiki.'; 30 | } 31 | -------------------------------------------------------------------------------- /php/midi/src/DiPlugin/Mock/FixCI.php: -------------------------------------------------------------------------------- 1 | load->library(\'didi_push_interface\');'; 48 | if ($bRecover) { 49 | $sCode = str_replace($sPatchLoad, $sOriginLoad, $sCode); 50 | } else { 51 | $sCode = str_replace($sOriginLoad, $sPatchLoad, $sCode); 52 | } 53 | 54 | file_put_contents($fPushFile, $sCode); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /php/midi/src/DiPlugin/Mock/MockFile.php: -------------------------------------------------------------------------------- 1 | [ 36 | 'namespace' => 'gs_api', 37 | 'name' => $contents[1], 38 | 'version' => 0, 39 | 'last_modify_time' => time(), 40 | 'log_rate' => 0, 41 | 'rule' => [ 42 | 'subject' => 'date_time_period', 43 | 'verb' => '=', 44 | 'objects' => [], 45 | ], 46 | 'publish_to' => [], 47 | 'schema_version' => '1.4.0', 48 | ], 49 | ]; 50 | if ($contents[2]) { 51 | $commonMockData['toggle']['rule']['objects'][] = [ 52 | date('Y-m-d', strtotime('-1 year')), 53 | date('Y-m-d', strtotime('+1 year')), 54 | ]; 55 | } else { 56 | $commonMockData['toggle']['rule']['objects'][] = [date('Y-m-d', strtotime('+1 year')), date('Y-m-d')]; 57 | } 58 | 59 | $mockFiles['/home/xiaoju/ep/as/store/toggles/' . $contents[1]] = base64_encode(json_encode($commonMockData)); 60 | $mockFiles['/home/xiaoju/ep/as/store//toggles/' . $contents[1]] = base64_encode(json_encode($commonMockData)); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /php/midi/src/DiPlugin/Parser/ParseApollo.php: -------------------------------------------------------------------------------- 1 | getPeer()->getIP() === '127.0.0.1' && $sendUDP->getPeer()->getPort() === 9891) { 30 | return true; 31 | } 32 | 33 | return false; 34 | } 35 | 36 | public static function parse($request, $response = null) 37 | { 38 | $items = explode("\t", $request); 39 | $count = count($items); 40 | 41 | if ($count == 2) { 42 | if ($items[0] == self::MISSING_TOGGLE) { 43 | return ['type' => self::MISSING_TOGGLE, 'toggle' => $items[1]]; 44 | } 45 | if ($items[0] == self::ACCESS_LOG) { 46 | return ['type' => self::ACCESS_LOG, 'log' => $items[1]]; 47 | } 48 | if ($items[0] == self::ERROR_LOG) { 49 | return ['type' => self::ERROR_LOG, 'log' => $items[1]]; 50 | } 51 | if ($items[0] == self::PUBLIC_LOG) { 52 | return ['type' => self::PUBLIC_LOG, 'log' => $items[1]]; 53 | } 54 | } 55 | 56 | if ($count == 3 && $items[0] == self::MISSING_CONFIG) { 57 | return ['type' => self::MISSING_CONFIG, 'ns' => $items[1], 'config' => $items[2],]; 58 | } 59 | 60 | if ($count == 5 && $items[0] == self::TOGGLE_METRICS && is_numeric($items[2]) && ($items[2] == 0 || $items[2] == 1)) { 61 | return ['type' => self::TOGGLE_METRICS, 'toggle' => $items[1], 'allow' => $items[2],]; 62 | } 63 | 64 | return false; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /php/midi/src/DiPlugin/README.md: -------------------------------------------------------------------------------- 1 | 2 | # DiDi Plugin 3 | 4 | DiDi Plugin used by our self. Here is an example of how to plugin midi. 5 | 6 | We also provide some commands like `search`, which could search session by request uri, response or upstream calls. 7 | 8 | --- 9 | 10 | DiPlugin 是滴滴内部在使用的一个插件。在 Midi 的基础上,针对内部做一些定制化的开发。 11 | 12 | 因为在滴滴内部,录制的 Session 最终会被存储在 Elastic 里,所以这边提供了 `search` 搜索命令,可以通过 URI、请求和响应的关键词 来搜索等。 13 | 14 | DiPlugin 作为插件的一个示例,放在这里供大家参考。因为要开源,所有对代码进行部分修改,并进行脱敏处理。 15 | 16 | --- 17 | 18 | ## DiPlugin Private Config 19 | 20 | - module-name 21 | 22 | - record-host 23 | 24 | - enable-disf 25 | - module-disf-name 26 | 27 | - enable-uploader 28 | - uploader-url 29 | 30 | - recommend-dsl-url 31 | 32 | - prepare-ci-system 33 | - ci-system-path 34 | - ci-system-git -------------------------------------------------------------------------------- /php/midi/src/DiPlugin/Reporter/Template/replayed-tab-apollo.twig: -------------------------------------------------------------------------------- 1 | {% if firstTab == 1 %} 2 | {% set tabActive = "active" %} 3 | {% else %} 4 | {% set tabActive = "" %} 5 | {% endif %} 6 | 7 | -------------------------------------------------------------------------------- /php/midi/src/DiPlugin/Resolver/EsDSL.php: -------------------------------------------------------------------------------- 1 | must[] = ['match_phrase' => ['Actions.Content' => $apollo,]]; 22 | return $this; 23 | } 24 | 25 | /** 26 | * @param array $params = [ 27 | * 'inbound_request' => 'key_word', 28 | * 'inbound_response' => 'key_word', 29 | * 'outbound_request' => 'key_word', 30 | * 'outbound_response' => 'key_word', 31 | * 'apollo' => 'key_word', 32 | * 'size' => 1, 33 | * 'begin' => 20180101, 34 | * 'end' => 20181231, 35 | * ] 36 | * 37 | * @param bool $withContext 38 | * @return EsDSL 39 | * @throws \Midi\Exception\Exception 40 | */ 41 | public function build($params, $withContext = true) 42 | { 43 | // search with record host name will be more accurate 44 | if ($withContext && empty($params['record-host'])) { 45 | $recordHost = DiConfig::getRecordHost(); 46 | if (!empty($recordHost)) { 47 | $params['record-host'] = $recordHost; 48 | } 49 | } 50 | 51 | parent::build($params, $withContext); 52 | 53 | if (!empty($params['apollo'])) { 54 | $this->apollo($params['apollo']); 55 | } 56 | 57 | return $this; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /php/midi/src/DiPlugin/Uploader/BaseMate.php: -------------------------------------------------------------------------------- 1 | ReplayMate::class, 22 | SearchCommand::class => SearchMate::class, 23 | ]; 24 | 25 | public static $mark = [ 26 | 'pid' => '', 27 | 'user' => '', 28 | 'project' => '', 29 | 'version' => '', 30 | 'command' => '', 31 | 'options' => '', 32 | 'status' => 1, 33 | 'msg' => '', 34 | 'action_at' => 0, 35 | ]; 36 | 37 | protected static $set = false; 38 | 39 | final public static function isSet() 40 | { 41 | return static::$set; 42 | } 43 | 44 | final public static function setUp() 45 | { 46 | static::$set = true; 47 | } 48 | 49 | final public static function enable() 50 | { 51 | static $enable; 52 | if ($enable === null) { 53 | /** @var Midi $midi */ 54 | $midi = Container::make("midi"); 55 | $isEnable = $midi->getConfig()->get('php', 'enable-uploader'); 56 | $enable = !empty($isEnable) ? true : false; 57 | } 58 | return $enable; 59 | } 60 | 61 | final public static function collect($class) 62 | { 63 | if (!self::enable()) { 64 | return; 65 | } 66 | 67 | $mateClass = self::MATCH[$class]; 68 | $mateClass::setUp(); 69 | 70 | if (self::isSet()) { 71 | return; 72 | } 73 | 74 | $input = Container::make('input'); 75 | 76 | self::$mark['pid'] = self::$mark['pid'] ?: Helper::guid($class::CMD); 77 | self::$mark['user'] = self::$mark['user'] ?: Helper::user(); 78 | self::$mark['project'] = self::$mark['project'] ?: DiConfig::getModuleName(); 79 | self::$mark['version'] = self::$mark['version'] ?: Application::getMidiVersion(); 80 | self::$mark['command'] = self::$mark['command'] ?: $class::CMD; 81 | self::$mark['options'] = self::$mark['options'] ?: Helper::optionFormat($input->getOptions()); 82 | self::$mark['action_at'] = self::$mark['action_at'] ?: strval(time()); 83 | 84 | self::setUp(); 85 | 86 | register_shutdown_function([Uploader::class, 'upload']); 87 | } 88 | 89 | abstract public static function getActions(); 90 | } 91 | -------------------------------------------------------------------------------- /php/midi/src/DiPlugin/Uploader/ReplayMate.php: -------------------------------------------------------------------------------- 1 | $status) { 30 | $mate = self::$mark; 31 | $mate['id'] = Helper::guid(self::$mark['command']); 32 | $mate['session'] = $session; 33 | $mate['latency'] = $status['latency']; 34 | $mate['same'] = $status['same']; 35 | $result[] = $mate; 36 | } 37 | 38 | return $result; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /php/midi/src/DiPlugin/Uploader/SearchMate.php: -------------------------------------------------------------------------------- 1 | getEventDispatcher()->dispatchCommandEvent( 25 | PluginEvents::PRE_COMMAND_RUN, $this->getName(), $input, $output 26 | ); 27 | 28 | parent::initialize($input, $output); 29 | } 30 | 31 | /** 32 | * @return Midi 33 | */ 34 | protected function getMidi() 35 | { 36 | /** @var Application $app */ 37 | $app = $this->getApplication(); 38 | 39 | return $app->getMidi(); 40 | } 41 | 42 | /** 43 | * @return EventDispatcher 44 | */ 45 | protected function getEventDispatcher() 46 | { 47 | return $this->getMidi()->getEventDispatcher(); 48 | } 49 | } -------------------------------------------------------------------------------- /php/midi/src/Midi/Config.yml: -------------------------------------------------------------------------------- 1 | rdebug-dir: /tmp/midi/ 2 | 3 | koala: 4 | inbound-port: 5514 5 | sub-port: 5515 6 | outbound-port: 5516 7 | replay-match-threshold: 0.75 8 | inbound-read-timeout: 86400s 9 | inbound-protocol: HTTP # HTTP or FastCGI, default is HTTP 10 | 11 | php: 12 | koala-php-ini: 13 | memory_limit: 2G 14 | error_log: /tmp/midi/log/php-error.log 15 | differ: Midi\Differ\Differ 16 | 17 | # for elastic 18 | preload-plugins: 19 | - Midi\ElasticPlugin 20 | session-resolver: Midi\Resolver\ElasticResolver 21 | 22 | # set your elastic search url 23 | # http://username:password@ip:port/Index/Type/_search 24 | elastic-search-url: 25 | -------------------------------------------------------------------------------- /php/midi/src/Midi/Container.php: -------------------------------------------------------------------------------- 1 | get($name); 36 | } 37 | 38 | public static function bind($name, $value) 39 | { 40 | return static::ins()->offsetSet($name, $value); 41 | } 42 | 43 | /** 44 | * Make from container. 45 | * 46 | * @param string $name . 47 | * 48 | * @return mixed 49 | * @throws ContainerException Error while retrieving the entry. 50 | * 51 | * @throws ContainerValueNotFoundException No entry was found for this name. 52 | */ 53 | public function get($name) 54 | { 55 | if (!$this->offsetExists($name)) { 56 | throw new ContainerValueNotFoundException(sprintf('Identifier "%s" is not found.', $name)); 57 | } 58 | try { 59 | return $this->offsetGet($name); 60 | } catch (\InvalidArgumentException $exception) { 61 | if ($this->exceptionThrownByContainer($exception)) { 62 | throw new ContainerException(sprintf('Container error while retrieving "%s"', $name), null, $exception); 63 | } else { 64 | throw $exception; 65 | } 66 | } 67 | } 68 | 69 | /** 70 | * Is exist in container. 71 | * 72 | * @param string $name 73 | * 74 | * @return boolean 75 | */ 76 | public function has($name) 77 | { 78 | return $this->offsetExists($name); 79 | } 80 | 81 | /** 82 | * Is thrown by pimple. 83 | * 84 | * @param \Exception $exception 85 | * 86 | * @return bool 87 | */ 88 | private function exceptionThrownByContainer(\Exception $exception) 89 | { 90 | $trace = $exception->getTrace()[0]; 91 | 92 | return $trace['class'] === PimpleContainer::class && $trace['function'] === 'offsetGet'; 93 | } 94 | } -------------------------------------------------------------------------------- /php/midi/src/Midi/Differ/DifferInterface.php: -------------------------------------------------------------------------------- 1 | 'onCommandConfigure', 29 | ); 30 | } 31 | 32 | public static function onCommandConfigure(CommandConfigureEvent $event) 33 | { 34 | ElasticResolver::onCommandConfigure($event); 35 | } 36 | } -------------------------------------------------------------------------------- /php/midi/src/Midi/EventDispatcher/Event.php: -------------------------------------------------------------------------------- 1 | name = $name; 42 | $this->midi = $midi; 43 | $this->args = $args; 44 | } 45 | 46 | /** 47 | * Returns the event's name. 48 | * 49 | * @return string The event name 50 | */ 51 | public function getName() 52 | { 53 | return $this->name; 54 | } 55 | 56 | /** 57 | * Returns the event's arguments. 58 | * 59 | * @return array The event arguments 60 | */ 61 | public function getArguments() 62 | { 63 | return $this->args; 64 | } 65 | 66 | /** 67 | * Returns the event's midi. 68 | * 69 | * @return Midi The event midi 70 | */ 71 | public function getMidi() 72 | { 73 | return $this->midi; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /php/midi/src/Midi/EventDispatcher/EventDispatcher.php: -------------------------------------------------------------------------------- 1 | midi = $midi; 30 | } 31 | 32 | public function dispatchEvent(string $name, array $args = array()) 33 | { 34 | $this->dispatch($name, new Event($name, $this->midi, $args)); 35 | } 36 | 37 | public function dispatchCommandEvent( 38 | string $name, 39 | string $commandName, 40 | InputInterface $input, 41 | OutputInterface $output, 42 | array $args = [] 43 | ) { 44 | if ($name === PluginEvents::PRE_COMMAND_RUN) { 45 | $event = new PreCommandEvent($name, $commandName, $this->midi, $input, $output, $args); 46 | } elseif ($name === PluginEvents::POST_COMMAND_RUN) { 47 | $event = new PostCommandEvent($name, $commandName, $this->midi, $input, $output, $args); 48 | } else { 49 | $event = new CommandEvent($name, $commandName, $this->midi, $input, $output, $args); 50 | } 51 | $this->dispatch($name, $event); 52 | } 53 | 54 | public function dispatchSolving(string $name, InputInterface $input, OutputInterface $output, array $args = array()) 55 | { 56 | $this->dispatch($name, new SessionsSolvingEvent($name, $this->midi, $input, $output, $args)); 57 | } 58 | } -------------------------------------------------------------------------------- /php/midi/src/Midi/EventDispatcher/EventSubscriberInterface.php: -------------------------------------------------------------------------------- 1 | setValue($value); 40 | } 41 | 42 | /** 43 | * @param mixed $value 44 | * @throws \ReflectionException 45 | */ 46 | public function setValue($value) 47 | { 48 | if (!static::isValid($value)) { 49 | throw new \InvalidArgumentException(sprintf("Invalid enumeration %s for Enum %s", $value, get_class($this))); 50 | } 51 | $this->value = $value; 52 | } 53 | 54 | /** 55 | * @return mixed 56 | */ 57 | public function getValue() 58 | { 59 | return $this->value; 60 | } 61 | 62 | /** 63 | * Check if the set value on this enum is a valid value for the enum 64 | * @param $value 65 | * @return boolean 66 | * @throws \ReflectionException 67 | */ 68 | public static function isValid($value) 69 | { 70 | if(!in_array($value, static::validValues(), true)) { 71 | return false; 72 | } 73 | return true; 74 | } 75 | 76 | 77 | /** 78 | * Get the valid values for this enum 79 | * Defaults to constants you define in your subclass 80 | * override to provide custom functionality 81 | * @param bool $assoc 82 | * @return array 83 | * @throws \ReflectionException 84 | */ 85 | public static function validValues($assoc = false) 86 | { 87 | $r = new \ReflectionClass(get_called_class()); 88 | if($assoc == true) { 89 | return $r->getConstants(); 90 | } else { 91 | return array_values($r->getConstants()); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /php/midi/src/Midi/Koala/Common/TypeConverter.php: -------------------------------------------------------------------------------- 1 | $element) { 60 | $list[$i] = call_user_func_array($convertBy, array_merge(array(&$val[$i]), $convertArgs)); 61 | } 62 | 63 | return $list; 64 | } 65 | } -------------------------------------------------------------------------------- /php/midi/src/Midi/Koala/Lexer.php: -------------------------------------------------------------------------------- 1 | 0) { 21 | $aRead = $matches[0]; 22 | } 23 | 24 | return array_merge($aRead, $aUnread); 25 | } 26 | 27 | public static function weightVector(string $text) 28 | { 29 | $vector = []; 30 | $tokens = self::lexer($text); 31 | if (count($tokens)) { 32 | foreach ($tokens as $token) { 33 | if (isset($vector[$token])) { 34 | ++$vector[$token]; 35 | } else { 36 | $vector[$token] = 1; 37 | } 38 | } 39 | } 40 | 41 | return $vector; 42 | } 43 | } -------------------------------------------------------------------------------- /php/midi/src/Midi/Koala/Matcher.php: -------------------------------------------------------------------------------- 1 | $aWeight) { 17 | if (isset($b[$aTerm])) { 18 | $prod += $aWeight * $b[$aTerm]; 19 | } 20 | $aSquareSum += $aWeight * $aWeight; 21 | } 22 | if ($aSquareSum == 0) { 23 | return 0; 24 | } 25 | 26 | foreach ($b as $bWeight) { 27 | $bSquareSum += $bWeight * $bWeight; 28 | } 29 | if ($bSquareSum == 0) { 30 | return 0; 31 | } 32 | 33 | return $prod / (sqrt($aSquareSum) * sqrt($bSquareSum)); 34 | } 35 | } -------------------------------------------------------------------------------- /php/midi/src/Midi/Koala/README.md: -------------------------------------------------------------------------------- 1 | # Koala 2 | 3 | This directories(Common, Replayed, Replaying) was autogenerated and host at internal repositories. 4 | For open source, we copy file here and simple modify. 5 | 6 | Koala.php、ParseRecorded.php is the core of midi. 7 | 8 | ParseRecorded.php parse online recorded session for offline replay: recordedSession to replayingSession. 9 | 10 | Koala.php will access koala inbound server and pass replayingSession. -------------------------------------------------------------------------------- /php/midi/src/Midi/Koala/Replayed/CallFunctionArg.php: -------------------------------------------------------------------------------- 1 | 1003, 9 | 'isUnion' => true, 10 | 'classObject' => CallFunctionArg::class, 11 | 'className' => 'Midi\Koala\Replayed\CallFunctionArg', 12 | 'annotations' => array(), 13 | 'fields' => array( 14 | ), 15 | ); 16 | 17 | public function __construct($array = null) 18 | { 19 | if (!isset($array)) { 20 | parent::__construct(array(), \ArrayObject::ARRAY_AS_PROPS); 21 | return; 22 | } 23 | 24 | parent::__construct($array, \ArrayObject::ARRAY_AS_PROPS); 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /php/midi/src/Midi/Koala/Replayed/Peer.php: -------------------------------------------------------------------------------- 1 | 1003, 19 | 'isUnion' => false, 20 | 'classObject' => Peer::class, 21 | 'className' => 'Midi\Koala\Replayed\Peer', 22 | 'annotations' => array(), 23 | 'fields' => array( 24 | "IP" => array( 25 | "fieldId" => 1, 26 | "thriftType" => 'STRING', 27 | "isRequired" => False, 28 | "annotations" => array(), 29 | ), 30 | "Port" => array( 31 | "fieldId" => 2, 32 | "thriftType" => 'I16', 33 | "isRequired" => False, 34 | "annotations" => array(), 35 | ), 36 | ), 37 | ); 38 | 39 | public function __construct($array = null) 40 | { 41 | if (!isset($array)) { 42 | parent::__construct(array(), \ArrayObject::ARRAY_AS_PROPS); 43 | return; 44 | } 45 | 46 | parent::__construct($array, \ArrayObject::ARRAY_AS_PROPS); 47 | 48 | if(isset($array["IP"])) { 49 | $this->setIP($array["IP"]); 50 | } 51 | 52 | if(isset($array["Port"])) { 53 | $this->setPort($array["Port"]); 54 | } 55 | 56 | } 57 | 58 | /** 59 | * @return string 60 | */ 61 | public function getIP()/* : string */ { 62 | return \Midi\Koala\Common\TypeConverter::to_string($this["IP"]); 63 | } 64 | 65 | /** 66 | * @param string $val 67 | */ 68 | public function setIP(/* string */ $val) { 69 | $this["IP"] = \Midi\Koala\Common\TypeConverter::to_string($val); 70 | } 71 | 72 | /** 73 | * @return int 74 | */ 75 | public function getPort()/* : int */ { 76 | return \Midi\Koala\Common\TypeConverter::to_int($this["Port"]); 77 | } 78 | 79 | /** 80 | * @param int $val 81 | */ 82 | public function setPort(/* int */ $val) { 83 | $this["Port"] = \Midi\Koala\Common\TypeConverter::to_int($val); 84 | } 85 | } 86 | 87 | /* THRIFT IDL 88 | namespace php koala.replayed 89 | 90 | struct Peer { 91 | 1: string IP 92 | 2: i16 Port 93 | } 94 | */ -------------------------------------------------------------------------------- /php/midi/src/Midi/Koala/Replayed/ReplayedAction.php: -------------------------------------------------------------------------------- 1 | 1003, 14 | 'isUnion' => true, 15 | 'classObject' => ReplayedAction::class, 16 | 'className' => 'Midi\Koala\Replayed\ReplayedAction', 17 | 'annotations' => array(), 18 | 'fields' => array( 19 | "ActionType" => array( 20 | "fieldId" => 3, 21 | "thriftType" => 'STRING', 22 | "isRequired" => False, 23 | "annotations" => array(), 24 | ), 25 | "OccurredAt" => array( 26 | "fieldId" => 2, 27 | "thriftType" => 'I64', 28 | "isRequired" => False, 29 | "annotations" => array(), 30 | ), 31 | "ActionId" => array( 32 | "fieldId" => 1, 33 | "thriftType" => 'STRING', 34 | "isRequired" => False, 35 | "annotations" => array(), 36 | ), 37 | ), 38 | ); 39 | 40 | public function __construct($array = null) 41 | { 42 | if (!isset($array)) { 43 | parent::__construct(array(), \ArrayObject::ARRAY_AS_PROPS); 44 | return; 45 | } 46 | 47 | parent::__construct($array, \ArrayObject::ARRAY_AS_PROPS); 48 | 49 | if(isset($array["ActionType"])) { 50 | $this->setActionType($array["ActionType"]); 51 | } 52 | 53 | if(isset($array["OccurredAt"])) { 54 | $this->setOccurredAt($array["OccurredAt"]); 55 | } 56 | 57 | if(isset($array["ActionId"])) { 58 | $this->setActionId($array["ActionId"]); 59 | } 60 | 61 | } 62 | 63 | /** 64 | * @return string 65 | */ 66 | public function getActionType()/* : string */ { 67 | return \Midi\Koala\Common\TypeConverter::to_string($this["ActionType"]); 68 | } 69 | 70 | /** 71 | * @param string $val 72 | */ 73 | public function setActionType(/* string */ $val) { 74 | $this["ActionType"] = \Midi\Koala\Common\TypeConverter::to_string($val); 75 | } 76 | 77 | /** 78 | * @return int 79 | */ 80 | public function getOccurredAt()/* : int */ { 81 | return \Midi\Koala\Common\TypeConverter::to_int($this["OccurredAt"]); 82 | } 83 | 84 | /** 85 | * @param int $val 86 | */ 87 | public function setOccurredAt(/* int */ $val) { 88 | $this["OccurredAt"] = \Midi\Koala\Common\TypeConverter::to_int($val); 89 | } 90 | 91 | /** 92 | * @return string 93 | */ 94 | public function getActionId()/* : string */ { 95 | return \Midi\Koala\Common\TypeConverter::to_string($this["ActionId"]); 96 | } 97 | 98 | /** 99 | * @param string $val 100 | */ 101 | public function setActionId(/* string */ $val) { 102 | $this["ActionId"] = \Midi\Koala\Common\TypeConverter::to_string($val); 103 | } 104 | } -------------------------------------------------------------------------------- /php/midi/src/Midi/Koala/Replayed/ReturnValue.php: -------------------------------------------------------------------------------- 1 | 1003, 9 | 'isUnion' => true, 10 | 'classObject' => ReturnValue::class, 11 | 'className' => 'Midi\Koala\Replayed\ReturnValue', 12 | 'annotations' => array(), 13 | 'fields' => array( 14 | ), 15 | ); 16 | 17 | public function __construct($array = null) 18 | { 19 | if (!isset($array)) { 20 | parent::__construct(array(), \ArrayObject::ARRAY_AS_PROPS); 21 | return; 22 | } 23 | 24 | parent::__construct($array, \ArrayObject::ARRAY_AS_PROPS); 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /php/midi/src/Midi/Koala/Replaying/Peer.php: -------------------------------------------------------------------------------- 1 | 1003, 19 | 'isUnion' => false, 20 | 'classObject' => Peer::class, 21 | 'className' => 'Midi\Koala\Replaying\Peer', 22 | 'annotations' => array(), 23 | 'fields' => array( 24 | "IP" => array( 25 | "fieldId" => 1, 26 | "thriftType" => 'STRING', 27 | "isRequired" => False, 28 | "annotations" => array(), 29 | ), 30 | "Port" => array( 31 | "fieldId" => 2, 32 | "thriftType" => 'I16', 33 | "isRequired" => False, 34 | "annotations" => array(), 35 | ), 36 | ), 37 | ); 38 | 39 | public function __construct($array = null) 40 | { 41 | if (!isset($array)) { 42 | parent::__construct(array(), \ArrayObject::ARRAY_AS_PROPS); 43 | return; 44 | } 45 | 46 | parent::__construct($array, \ArrayObject::ARRAY_AS_PROPS); 47 | 48 | if(isset($array["IP"])) { 49 | $this->setIP($array["IP"]); 50 | } 51 | 52 | if(isset($array["Port"])) { 53 | $this->setPort($array["Port"]); 54 | } 55 | 56 | } 57 | 58 | /** 59 | * @return string 60 | */ 61 | public function getIP()/* : string */ { 62 | return \Midi\Koala\Common\TypeConverter::to_string($this["IP"]); 63 | } 64 | 65 | /** 66 | * @param string $val 67 | */ 68 | public function setIP(/* string */ $val) { 69 | $this["IP"] = \Midi\Koala\Common\TypeConverter::to_string($val); 70 | } 71 | 72 | /** 73 | * @return int 74 | */ 75 | public function getPort()/* : int */ { 76 | return \Midi\Koala\Common\TypeConverter::to_int($this["Port"]); 77 | } 78 | 79 | /** 80 | * @param int $val 81 | */ 82 | public function setPort(/* int */ $val) { 83 | $this["Port"] = \Midi\Koala\Common\TypeConverter::to_int($val); 84 | } 85 | } 86 | 87 | /* THRIFT IDL 88 | namespace php koala 89 | 90 | struct Peer { 91 | 1: string IP 92 | 2: i16 Port 93 | } 94 | */ -------------------------------------------------------------------------------- /php/midi/src/Midi/Message.php: -------------------------------------------------------------------------------- 1 | Summary: Replayed %d Sessions No DIFF. Spent %d s%s 9 | 10 | EOF; 11 | 12 | const SUMMARY_DO_REPLAY_EXIST_DIFF = <<Summary: Replayed %d Sessions %d DIFFERENT. Spent %d s%s 14 | Diff sessionId: %s 15 | Retry failed session: midi run -s %s 16 | EOF; 17 | 18 | const SUMMARY_NO_REPLAY = 'Summary: Replayed 0 Sessions. Spent %d s%s'; 19 | 20 | // run command 21 | const RUN_COMMAND_INVALID_PARAMS = 'Require files, use `-f /path/to/session.json`, get more usage `midi help run`.'; 22 | 23 | const RUN_COMMAND_ELASTIC_INVALID_PARAMS = 'Require uri or sessionId or file, get usage `midi help run`.'; 24 | 25 | const RUN_COMMAND_NOT_FOUND_XDEBUG = 'Can not find `xdebug` extension, -x option will no work.'; 26 | 27 | const RUN_COMMAND_NOT_FOUND_COVERAGE = 'Can not find `xdebug` extension, -C option will no work.'; 28 | 29 | const RUN_COMMAND_NOT_FOUND_TRACE = 'Can not find `xdebug` extension, -T option will no work.'; 30 | 31 | const RUN_COMMAND_NO_SESSION_REPLAY = 'No sessions for test.'; 32 | 33 | const RUN_COMMAND_REPLAYER_NOT_START = 'Replayer not start up, try later.'; 34 | } 35 | -------------------------------------------------------------------------------- /php/midi/src/Midi/Midi.php: -------------------------------------------------------------------------------- 1 | config; 53 | } 54 | 55 | /** 56 | * @param Config $config 57 | */ 58 | public function setConfig(Config $config) 59 | { 60 | $this->config = $config; 61 | } 62 | 63 | /** 64 | * @return EventDispatcher 65 | */ 66 | public function getEventDispatcher(): EventDispatcher 67 | { 68 | return $this->eventDispatcher; 69 | } 70 | 71 | /** 72 | * @param EventDispatcher $eventDispatcher 73 | */ 74 | public function setEventDispatcher(EventDispatcher $eventDispatcher) 75 | { 76 | $this->eventDispatcher = $eventDispatcher; 77 | } 78 | 79 | /** 80 | * @return Koala 81 | */ 82 | public function getKoala(): Koala 83 | { 84 | return $this->koala; 85 | } 86 | 87 | /** 88 | * @param Koala $koala 89 | */ 90 | public function setKoala(Koala $koala) 91 | { 92 | $this->koala = $koala; 93 | } 94 | 95 | /** 96 | * @return ResolverInterface 97 | */ 98 | public function getResolver(): ResolverInterface 99 | { 100 | return $this->resolver; 101 | } 102 | 103 | /** 104 | * @param ResolverInterface $resolver 105 | */ 106 | public function setResolver(ResolverInterface $resolver) 107 | { 108 | $this->resolver = $resolver; 109 | } 110 | 111 | /** 112 | * @return DifferInterface 113 | */ 114 | public function getDiffer(): DifferInterface 115 | { 116 | return $this->differ; 117 | } 118 | 119 | /** 120 | * @param DifferInterface $differ 121 | */ 122 | public function setDiffer(DifferInterface $differ) 123 | { 124 | $this->differ = $differ; 125 | } 126 | 127 | /** 128 | * @return ReportInterface 129 | */ 130 | public function getReporter(): ReportInterface 131 | { 132 | return $this->reporter; 133 | } 134 | 135 | /** 136 | * @param ReportInterface $reporter 137 | */ 138 | public function setReporter(ReportInterface $reporter) 139 | { 140 | $this->reporter = $reporter; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /php/midi/src/Midi/Mock/MockDir.php: -------------------------------------------------------------------------------- 1 | get('redirect-dir'); 22 | if ($redirect === null) { 23 | $redirect = []; 24 | } 25 | 26 | $cwd = Container::make('workingDir'); 27 | $deployPath = $config->get('php', 'deploy-path'); 28 | if (!empty($deployPath) && $deployPath !== $cwd) { 29 | $redirect[$deployPath] = $cwd; 30 | } 31 | 32 | return $redirect; 33 | } 34 | } -------------------------------------------------------------------------------- /php/midi/src/Midi/Mock/MockStorage.php: -------------------------------------------------------------------------------- 1 | 34 | 35 | CODE; 36 | 37 | $patchCode = ''; 38 | $recordKeys = []; 39 | 40 | // the same key keep the last one 41 | foreach (array_reverse($actions) as $action) { 42 | if ($action['ActionType'] != 'ReadStorage') { 43 | continue; 44 | } 45 | $contents = explode("\n", $action['Content']); 46 | if ($contents[0] !== 'apcu_fetch') { 47 | continue; 48 | } 49 | $key = stripcslashes($contents[1]); 50 | if (isset($recordKeys[$key])) { 51 | continue; 52 | } 53 | $recordKeys[$key] = 1; 54 | $val = $contents[2]; 55 | $initCode = <<getType() == FCGI::PARAMS) { 20 | /* @var $record Params */ 21 | $fcgiParams = array_merge($fcgiParams, $record->getValues()); 22 | } elseif ($record->getType() == FCGI::STDIN) { 23 | /* @var $record Stdin */ 24 | if ($record->getContentLength() > 0) { 25 | $fcgiStdin .= $record->getContentData(); 26 | } 27 | } 28 | } 29 | 30 | // request headers & body 31 | return ['params' => $fcgiParams, 'stdin' => $fcgiStdin,]; 32 | } 33 | 34 | public static function parseResponse($fcgiResp) 35 | { 36 | $resp = ''; 37 | while (FrameParser::hasFrame($fcgiResp)) { 38 | $record = FrameParser::parseFrame($fcgiResp); 39 | if ($record->getType() == FCGI::STDOUT) { 40 | $resp .= $record->getContentData(); 41 | } 42 | } 43 | 44 | return $resp; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /php/midi/src/Midi/Parser/ParseHTTP.php: -------------------------------------------------------------------------------- 1 | $method, 36 | 'url' => $url, 37 | 'uri' => $uri, 38 | 'version' => trim($version), 39 | 'continue100' => $continue100, 40 | 'body' => $aReq[1] ?? '', 41 | ]; 42 | } 43 | 44 | public static function parseResp($response) 45 | { 46 | $lineIdx = strpos($response, "\n"); 47 | $responseLine = substr($response, 0, $lineIdx); 48 | list($version, $httpCode, $httpDesc) = explode(' ', $responseLine); 49 | 50 | $aResp = explode("\r\n\r\n", $response); 51 | $aHeader = explode("\r\n", $aResp[0]); 52 | $sBody = $aResp[1]; 53 | 54 | return ['version' => $version, 'httpCode' => intval($httpCode), 'header' => $aHeader, 'body' => trim($sBody),]; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /php/midi/src/Midi/Parser/ParseInterface.php: -------------------------------------------------------------------------------- 1 | pushIncoming($request) as $command) { 37 | /* @var $command \Clue\Redis\Protocol\Model\Request */ 38 | $commands[] = ['command' => $command->getCommand(), 'args' => $command->getArgs(),]; 39 | } 40 | return $commands; 41 | } 42 | } -------------------------------------------------------------------------------- /php/midi/src/Midi/Parser/ParseReplayedInterface.php: -------------------------------------------------------------------------------- 1 | readMessageBegin($methodName, $messageType, $messageSeqId); 27 | } catch (\Exception $e) { 28 | return false; 29 | } 30 | 31 | if (preg_match('~(\w+).*?(\w+).*?(\w+).{3}(\S{30})~', $request, $matches)) { 32 | list($_, $_, $name, $id, $detail) = $matches; 33 | $content = "$name $id - $detail"; 34 | } else { 35 | $content = substr($request, 0, 40); 36 | } 37 | 38 | return [ 39 | 'methodName' => $methodName, 40 | 'messageType' => $messageType, 41 | 'messageSeqId' => $messageSeqId, 42 | 'content' => $content, 43 | ]; 44 | } 45 | } 46 | 47 | 48 | -------------------------------------------------------------------------------- /php/midi/src/Midi/Parser/ParseThrift.php: -------------------------------------------------------------------------------- 1 | readMessageBegin($methodName, $messageType, $messageSeqId); 27 | } catch (\Exception $e) { 28 | return false; 29 | } 30 | 31 | return ['methodName' => $methodName, 'messageType' => $messageType, 'messageSeqId' => $messageSeqId,]; 32 | } 33 | } 34 | 35 | 36 | -------------------------------------------------------------------------------- /php/midi/src/Midi/Plugin/Event/CommandConfigureEvent.php: -------------------------------------------------------------------------------- 1 | command = $command; 22 | } 23 | 24 | public function addArgument($name, $mode = null, $description = '', $default = null) 25 | { 26 | $this->command->addArgument($name, $mode, $description, $default); 27 | return $this; 28 | } 29 | 30 | public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null) 31 | { 32 | $this->command->addOption($name, $shortcut, $mode, $description, $default); 33 | return $this; 34 | } 35 | 36 | public function getName() 37 | { 38 | return $this->command->getName(); 39 | } 40 | 41 | public function setDescription($description) 42 | { 43 | $this->command->setDescription($description); 44 | return $this; 45 | } 46 | } -------------------------------------------------------------------------------- /php/midi/src/Midi/Plugin/Event/CommandEvent.php: -------------------------------------------------------------------------------- 1 | commandName = $commandName; 41 | $this->input = $input; 42 | $this->output = $output; 43 | } 44 | 45 | /** 46 | * Returns the command input interface 47 | * 48 | * @return InputInterface 49 | */ 50 | public function getInput() 51 | { 52 | return $this->input; 53 | } 54 | 55 | /** 56 | * Retrieves the command output interface 57 | * 58 | * @return OutputInterface 59 | */ 60 | public function getOutput() 61 | { 62 | return $this->output; 63 | } 64 | 65 | /** 66 | * Retrieves the name of the command being run 67 | * 68 | * @return string 69 | */ 70 | public function getCommandName() 71 | { 72 | return $this->commandName; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /php/midi/src/Midi/Plugin/Event/PostCommandEvent.php: -------------------------------------------------------------------------------- 1 | callFromInbound = $callFromIndbound; 45 | $this->returnInbound = $returnInbound; 46 | $this->actions = $actions; 47 | $this->mockFiles = $mockFiles ?? []; 48 | $this->redirectDirs = $redirectDirs ?? []; 49 | } 50 | 51 | /** 52 | * @return CallFromInbound 53 | */ 54 | public function getCallFromInbound() 55 | { 56 | return $this->callFromInbound; 57 | } 58 | 59 | public function setCallFromInbound($callFromInbound) 60 | { 61 | $this->callFromInbound = $callFromInbound; 62 | } 63 | 64 | public function getReturnInbound() 65 | { 66 | return $this->returnInbound; 67 | } 68 | 69 | public function setReturnInbound($returnInbound) 70 | { 71 | $this->returnInbound = $returnInbound; 72 | } 73 | 74 | public function getActions() 75 | { 76 | return $this->actions; 77 | } 78 | 79 | public function setActions($actions) 80 | { 81 | $this->actions = $actions; 82 | } 83 | 84 | public function getMockFiles() 85 | { 86 | return $this->mockFiles; 87 | } 88 | 89 | public function setMockFiles($mockFiles) 90 | { 91 | $this->mockFiles = $mockFiles; 92 | } 93 | 94 | public function getRedirectDirs() 95 | { 96 | return $this->redirectDirs; 97 | } 98 | 99 | public function setRedirectDirs($redirectDirs) 100 | { 101 | $this->redirectDirs = $redirectDirs; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /php/midi/src/Midi/Plugin/Event/PostReplaySessionEvent.php: -------------------------------------------------------------------------------- 1 | replayedSession = $replayedSession; 33 | $this->args = $args; 34 | } 35 | 36 | /** 37 | * Returns the replayed session 38 | * 39 | * @return ReplayedSession 40 | */ 41 | public function getReplayedSession() 42 | { 43 | return $this->replayedSession; 44 | } 45 | 46 | /** 47 | * @param ReplayedSession $replayedSession 48 | */ 49 | public function setReplayedSession(ReplayedSession $replayedSession) 50 | { 51 | $this->replayedSession = $replayedSession; 52 | } 53 | 54 | /** 55 | * @return array 56 | */ 57 | public function getArgs() 58 | { 59 | return $this->args; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /php/midi/src/Midi/Plugin/Event/PreCommandEvent.php: -------------------------------------------------------------------------------- 1 | app = $app; 36 | $this->options = $options; 37 | $this->output = $output; 38 | } 39 | 40 | /** 41 | * @return Command 42 | */ 43 | public function getCommand($name) 44 | { 45 | return $this->app->find($name); 46 | } 47 | 48 | public function getOptions() 49 | { 50 | return $this->options; 51 | } 52 | 53 | public function setOptions($options) 54 | { 55 | $this->options = $options; 56 | } 57 | 58 | public function getOutput() 59 | { 60 | return $this->output; 61 | } 62 | } -------------------------------------------------------------------------------- /php/midi/src/Midi/Plugin/Event/PreReplaySessionEvent.php: -------------------------------------------------------------------------------- 1 | replayingSession = $replayingSession; 27 | } 28 | 29 | /** 30 | * Returns the replaying session 31 | * 32 | * @return ReplayingSession 33 | */ 34 | public function getReplayingSession() 35 | { 36 | return $this->replayingSession; 37 | } 38 | 39 | public function setReplayingSession($replayingSession) 40 | { 41 | $this->replayingSession = $replayingSession; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /php/midi/src/Midi/Plugin/Event/SessionsSolvingEvent.php: -------------------------------------------------------------------------------- 1 | input = $input; 35 | $this->output = $output; 36 | } 37 | 38 | /** 39 | * Returns the command input interface 40 | * 41 | * @return InputInterface 42 | */ 43 | public function getInput() 44 | { 45 | return $this->input; 46 | } 47 | 48 | /** 49 | * Retrieves the command output interface 50 | * 51 | * @return OutputInterface 52 | */ 53 | public function getOutput() 54 | { 55 | return $this->output; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /php/midi/src/Midi/Plugin/PluginEvents.php: -------------------------------------------------------------------------------- 1 | getOption('file'); 28 | if (empty($files)) { 29 | throw new ResolveInvalidParam(Message::RUN_COMMAND_INVALID_PARAMS); 30 | } 31 | 32 | $sessions = []; 33 | foreach ($files as $file) { 34 | $file = trim($file); 35 | if (!file_exists($file)) { 36 | $output->writeln("Session file '$file' not found!"); 37 | continue; 38 | } 39 | $session = json_decode(file_get_contents($file), true); 40 | if (json_last_error() !== JSON_ERROR_NONE) { 41 | $output->writeln("Session file '$file' not invalid json!"); 42 | continue; 43 | } 44 | $sessions[] = $session; 45 | } 46 | 47 | return $sessions; 48 | } 49 | } -------------------------------------------------------------------------------- /php/midi/src/Midi/Resolver/ResolverInterface.php: -------------------------------------------------------------------------------- 1 | start($name); 39 | } 40 | 41 | public static function stop($name, $verbosity = OutputInterface::VERBOSITY_VERBOSE) 42 | { 43 | self::init(); 44 | $event = self::$stopWatch->stop($name); 45 | $duration = $event->getDuration(); 46 | self::$output->writeln("$name spent $duration ms", $verbosity); 47 | return $duration; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /php/midi/src/Midi/Util/OS.php: -------------------------------------------------------------------------------- 1 | assertSame($config->get('koala', 'inbound-port'), 5514); 14 | $config->merge([ 15 | 'koala' => [ 16 | 'inbound-port' => 6515, 17 | ], 18 | ]); 19 | $this->assertSame($config->get('koala', 'inbound-port'), 6515); 20 | } 21 | 22 | public function testConfigMerge() 23 | { 24 | $config = new Config(); 25 | $this->assertSame($config->get('php', 'preload-plugins'), ['Midi\ElasticPlugin']); 26 | 27 | $config->merge([ 28 | 'php' => [ 29 | 'preload-plugins' => [ 30 | 'Midi\Plugin', 31 | ], 32 | ], 33 | ]); 34 | $this->assertSame($config->get('php', 'preload-plugins'), ['Midi\ElasticPlugin', 'Midi\Plugin']); 35 | } 36 | 37 | public function testMergeUniq() 38 | { 39 | $config = new Config(); 40 | $config->merge([ 41 | 'php' => [ 42 | 'preload-plugins' => [ 43 | 'Midi\Plugin', 44 | 'Midi\Plugin', 45 | ], 46 | ], 47 | ]); 48 | $this->assertSame($config->get('php', 'preload-plugins'), ['Midi\ElasticPlugin', 'Midi\Plugin']); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /php/midi/tests/Midi/Test/ContainerTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(Container::class, $c); 15 | } 16 | 17 | /** 18 | * @expectedException \Midi\Exception\ContainerValueNotFoundException 19 | */ 20 | public function testNotFound() 21 | { 22 | Container::make('abc'); 23 | } 24 | 25 | public function testBind() 26 | { 27 | Container::bind('abc', 'abc'); 28 | $this->assertSame(Container::make('abc'), 'abc'); 29 | } 30 | 31 | public function testNotHas() 32 | { 33 | $this->assertNotTrue(Container::ins()->has('bar')); 34 | } 35 | 36 | public function testHas() 37 | { 38 | Container::bind('foo', 'abc'); 39 | $this->assertTrue(Container::ins()->has('foo')); 40 | } 41 | 42 | public function testSingleton() 43 | { 44 | $obj = new \stdClass(); 45 | Container::bind('abc', $obj); 46 | 47 | $a1 = Container::make('abc'); 48 | $a2 = Container::make('abc'); 49 | $this->assertSame(spl_object_hash($a1), spl_object_hash($a2)); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /php/midi/tests/Midi/Test/Koala/MatcherTest.php: -------------------------------------------------------------------------------- 1 | assertSame(Matcher::cosineSimilarity([], []), 0); 13 | } 14 | 15 | public function test50Similarity() 16 | { 17 | $a = [1 => 1, 2 => 1, 3 => 1, 4 => 1,]; 18 | $b = [1 => 1, 3 => 1, 5 => 1, 7 => 1,]; 19 | $this->assertSame(Matcher::cosineSimilarity($a, $b), 0.5); 20 | } 21 | 22 | public function test100Similarity() 23 | { 24 | $a = [1 => 1, 2 => 1, 3 => 1, 4 => 1,]; 25 | $b = [1 => 1, 2 => 1, 3 => 1, 4 => 1,]; 26 | $this->assertSame(Matcher::cosineSimilarity($a, $b), 1.0); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /php/midi/tests/Midi/Test/Parser/ParseHTTPTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(ParseHTTP::match($request), true); 23 | $this->assertEquals(ParseHTTP::parse($request), 24 | [ 25 | 'method' => "POST", 26 | 'url' => "/service/wsgsigCheck", 27 | 'uri' => "/service/wsgsigCheck", 28 | 'version' => "HTTP/1.1", 29 | 'continue100' => 1, 30 | 'body' => '', 31 | ] 32 | ); 33 | } 34 | 35 | public function testResponse() { 36 | $response = <<assertSame(ParseHTTP::parseResp($response), 41 | [ 42 | 'version' => 'HTTP/1.1', 43 | 'httpCode' => 100, 44 | 'header' => [ 45 | "HTTP/1.1 100 Continue\n" 46 | ], 47 | 'body' => '', 48 | ] 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /php/midi/tests/Midi/Test/Parser/ParseMySQLTest.php: -------------------------------------------------------------------------------- 1 | assertSame(ParseMySQL::match($request), ParseMySQL::TYPE_C_S_COM); 14 | } 15 | 16 | public function testAuthPass() { 17 | $response = "\x07\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00"; 18 | $this->assertSame(ParseMySQL::isAuthOk($response), true); 19 | } 20 | 21 | public function testUnpack() { 22 | $request = "\x07\x00\x00\x00\x02gs_abc"; 23 | $this->assertSame(ParseMySQL::match($request), ParseMySQL::TYPE_C_S_COM); 24 | $this->assertSame(ParseMySQL::getMessageLen($request), 7); 25 | $this->assertSame(ParseMySQL::validMessageLen($request), true); 26 | $this->assertSame(ParseMySQL::getReqMessageType($request), "\x02"); 27 | $this->assertSame(ParseMySQL::toString($request), 'USE gs_abc'); 28 | 29 | $request = "\x0f\x00\x00\x00\x03SET NAMES utf8"; 30 | $this->assertSame(ParseMySQL::getMessageLen($request), 15); 31 | $this->assertSame(ParseMySQL::validMessageLen($request), true); 32 | $this->assertSame(ParseMySQL::getReqMessageType($request), "\x03"); 33 | $this->assertSame(ParseMySQL::toString($request), "SET NAMES utf8"); 34 | } 35 | 36 | public function testHand() { 37 | $this->markTestSkipped("need set body"); 38 | // add request to $req 39 | $req = ""; 40 | $this->assertSame(ParseMySQL::isLoginAuth($req), true); 41 | } 42 | 43 | public function testisHandInit() { 44 | $this->markTestSkipped("need set body"); 45 | // add response to $response 46 | $response = ""; 47 | $this->assertSame(ParseMySQL::isHandShake($response), true); 48 | 49 | $protocol = unpack("C", $response[4]); 50 | $this->assertSame($protocol[1], 10); 51 | 52 | $response = substr($response, 5); 53 | $versionPos = strpos($response, "\x00"); 54 | $serverVersionOriginal = substr($response, 0, $versionPos); 55 | 56 | $response = substr($response, $versionPos + 1); 57 | $threadId = substr($response, 0, 4); 58 | 59 | $authData1 = substr($response, 4, 8); 60 | 61 | $pad = $response[12]; 62 | $this->assertSame($pad, "\x00"); 63 | 64 | $response = substr($response, 13); 65 | $charset = unpack("C", $response[3]); 66 | 67 | $this->assertSame(substr($response, 8, 10), "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /php/midi/tests/bootstrap.php: -------------------------------------------------------------------------------- 1 |