瀏覽代碼

HUE-8936 [tracing] Adding jaeger-client to extpy

Romain 6 年之前
父節點
當前提交
582c681e9d
共有 80 個文件被更改,包括 12657 次插入0 次删除
  1. 199 0
      desktop/core/ext-py/jaeger-client-4.0.0/CHANGELOG.rst
  2. 150 0
      desktop/core/ext-py/jaeger-client-4.0.0/CONTRIBUTING.md
  3. 10 0
      desktop/core/ext-py/jaeger-client-4.0.0/MANIFEST.in
  4. 107 0
      desktop/core/ext-py/jaeger-client-4.0.0/Makefile
  5. 19 0
      desktop/core/ext-py/jaeger-client-4.0.0/PKG-INFO
  6. 192 0
      desktop/core/ext-py/jaeger-client-4.0.0/README.md
  7. 22 0
      desktop/core/ext-py/jaeger-client-4.0.0/RELEASE.md
  8. 13 0
      desktop/core/ext-py/jaeger-client-4.0.0/crossdock/server/__init__.py
  9. 15 0
      desktop/core/ext-py/jaeger-client-4.0.0/crossdock/server/constants.py
  10. 150 0
      desktop/core/ext-py/jaeger-client-4.0.0/crossdock/server/endtoend.py
  11. 122 0
      desktop/core/ext-py/jaeger-client-4.0.0/crossdock/server/serializer.py
  12. 226 0
      desktop/core/ext-py/jaeger-client-4.0.0/crossdock/server/server.py
  13. 42 0
      desktop/core/ext-py/jaeger-client-4.0.0/crossdock/server/thriftrw_serializer.py
  14. 1 0
      desktop/core/ext-py/jaeger-client-4.0.0/crossdock/thrift_gen/__init__.py
  15. 448 0
      desktop/core/ext-py/jaeger-client-4.0.0/crossdock/thrift_gen/tracetest/TracedService.py
  16. 1 0
      desktop/core/ext-py/jaeger-client-4.0.0/crossdock/thrift_gen/tracetest/__init__.py
  17. 11 0
      desktop/core/ext-py/jaeger-client-4.0.0/crossdock/thrift_gen/tracetest/constants.py
  18. 564 0
      desktop/core/ext-py/jaeger-client-4.0.0/crossdock/thrift_gen/tracetest/ttypes.py
  19. 55 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/TUDPTransport.py
  20. 33 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/__init__.py
  21. 321 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/codecs.py
  22. 442 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/config.py
  23. 97 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/constants.py
  24. 72 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/ioloop_util.py
  25. 105 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/local_agent_net.py
  26. 19 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/metrics/__init__.py
  27. 132 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/metrics/metrics.py
  28. 61 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/metrics/prometheus.py
  29. 70 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/rate_limiter.py
  30. 277 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/reporter.py
  31. 538 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/sampler.py
  32. 202 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/span.py
  33. 71 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/span_context.py
  34. 239 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift.py
  35. 0 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/__init__.py
  36. 286 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/agent/Agent.py
  37. 1 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/agent/__init__.py
  38. 13 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/agent/constants.py
  39. 23 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/agent/ttypes.py
  40. 283 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/jaeger/Collector.py
  41. 1 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/jaeger/__init__.py
  42. 13 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/jaeger/constants.py
  43. 893 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/jaeger/ttypes.py
  44. 266 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/sampling/SamplingManager.py
  45. 1 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/sampling/__init__.py
  46. 13 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/sampling/constants.py
  47. 479 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/sampling/ttypes.py
  48. 283 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/zipkincore/ZipkinCollector.py
  49. 1 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/zipkincore/__init__.py
  50. 29 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/zipkincore/constants.py
  51. 695 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/zipkincore/ttypes.py
  52. 198 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/throttler.py
  53. 342 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/tracer.py
  54. 117 0
      desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/utils.py
  55. 1 0
      desktop/core/ext-py/jaeger-client-4.0.0/requirements-dev.txt
  56. 5 0
      desktop/core/ext-py/jaeger-client-4.0.0/requirements-tests.txt
  57. 3 0
      desktop/core/ext-py/jaeger-client-4.0.0/requirements.txt
  58. 28 0
      desktop/core/ext-py/jaeger-client-4.0.0/setup.cfg
  59. 74 0
      desktop/core/ext-py/jaeger-client-4.0.0/setup.py
  60. 13 0
      desktop/core/ext-py/jaeger-client-4.0.0/tests/__init__.py
  61. 30 0
      desktop/core/ext-py/jaeger-client-4.0.0/tests/conftest.py
  62. 47 0
      desktop/core/ext-py/jaeger-client-4.0.0/tests/test_TUDPTransport.py
  63. 39 0
      desktop/core/ext-py/jaeger-client-4.0.0/tests/test_api.py
  64. 566 0
      desktop/core/ext-py/jaeger-client-4.0.0/tests/test_codecs.py
  65. 175 0
      desktop/core/ext-py/jaeger-client-4.0.0/tests/test_config.py
  66. 171 0
      desktop/core/ext-py/jaeger-client-4.0.0/tests/test_crossdock.py
  67. 92 0
      desktop/core/ext-py/jaeger-client-4.0.0/tests/test_local_agent_net.py
  68. 99 0
      desktop/core/ext-py/jaeger-client-4.0.0/tests/test_metrics.py
  69. 60 0
      desktop/core/ext-py/jaeger-client-4.0.0/tests/test_noop_tracer.py
  70. 56 0
      desktop/core/ext-py/jaeger-client-4.0.0/tests/test_prometheus.py
  71. 85 0
      desktop/core/ext-py/jaeger-client-4.0.0/tests/test_rate_limiter.py
  72. 298 0
      desktop/core/ext-py/jaeger-client-4.0.0/tests/test_reporter.py
  73. 723 0
      desktop/core/ext-py/jaeger-client-4.0.0/tests/test_sampler.py
  74. 255 0
      desktop/core/ext-py/jaeger-client-4.0.0/tests/test_span.py
  75. 49 0
      desktop/core/ext-py/jaeger-client-4.0.0/tests/test_span_context.py
  76. 188 0
      desktop/core/ext-py/jaeger-client-4.0.0/tests/test_thrift.py
  77. 101 0
      desktop/core/ext-py/jaeger-client-4.0.0/tests/test_throttler.py
  78. 361 0
      desktop/core/ext-py/jaeger-client-4.0.0/tests/test_tracer.py
  79. 92 0
      desktop/core/ext-py/jaeger-client-4.0.0/tests/test_tracer_benchmark.py
  80. 83 0
      desktop/core/ext-py/jaeger-client-4.0.0/tests/test_utils.py

+ 199 - 0
desktop/core/ext-py/jaeger-client-4.0.0/CHANGELOG.rst

@@ -0,0 +1,199 @@
+.. :changelog:
+
+History
+-------
+
+4.0.0 (2019-04-27)
+------------------
+
+- (Support OpenTracing API v2.0 (#206) <Michał Szymański>
+- Add support for references (#241) <Bhavin Gandhi>
+- Add info on configuring Jaeger agent location. (#245) <Carlos Nunez>
+- Add 128bit trace_id support (#230) <Bhavin Gandhi>
+- Enable linting of tests (#227) <pravarag>
+- Enable windows support by isolating ioctl calls (#233) <CARRIERE Etienne>
+- Unpin flake8 to allow v3 (#231) <Yuri Shkuro>
+- Fix pycurl install advice <Yuri Shkuro>
+- Fix handling of missing headers in the b3 codec (#215) <cshowe>
+- Fix typo in the link name <Yuri Shkuro>
+- Add supported python version to README (#226) <Won Jun Jang>
+
+
+3.13.0 (2018-12-04)
+-------------------
+
+- Mark reporter_queue_size Config option as valid (#209) <Tim Joseph Dumol>
+- Allow setting of the Jaeger Agent Host via environment variable (#218) <Tim Stoop>
+- Wrap parsing of throttling response in try block to avoid errors (#219) <Isaac Hier>
+
+
+3.12.0 (2018-11-02)
+-------------------
+
+- Support deleting key from baggage (#216) <TianYi ZHU>
+- Preserve datatype of tag values (#213) <Trond Hindenes>
+- Support `jaeger-baggage` header for ad-hoc baggage (#207) <Yuri Shkuro>
+
+
+3.11.0 (2018-08-02)
+-------------------
+
+- Fix parsing remote sampling strategy on Python 3.5 (#180) <Niels Pardon>
+- Fix handling of missing ParentSpanId for B3 codec (#182) <Gabriel Gravel>
+- Implement throttler (#188) <Isaac Hier>
+- Improve handling of config values and default arguments (#192) <Isaac Hier>
+- Add ability to update rate limitier (#196) <Won Jun Jang>
+- Fix incorrect default agent reporting port (#200) <Ryan Fitzpatrick>
+- Correct sampled and debug B3 headers (#203) <Ryan Fitzpatrick>
+
+
+3.10.0 (2018-05-09)
+------------------
+
+- Allow specifying hostname and ip via tags (#167) <Jeff Schroeder>
+- Use String strategyType instead of int (#172)
+
+
+3.9.0 (2018-04-20)
+------------------
+
+Python 3.6 is now officially supported!
+
+- Remove support for non-ascii baggage keys; enable testing with Py 3.6  (#154) <Yuri Shkuro>
+- Add IP tag to tracer tags; rename `jaeger.hostname` tag to `hostname` (#160) <Won Jun Jang>
+- Remove `tchannel;<py3` restriction in tests/extras_require (#159) <Won Jun Jang>
+- Replace `concurrent.futures.Future` with `tornado.concurrent.Future` (#155) <Yuri Shkuro>
+- Better support for creating multiple tracers (#150) <nziebart>
+- Add PrometheusMetricsFactory (#142) <Eundoo Song>
+- Add ability to validate configuration (#124) <Gregory Reshetniak>
+- Make Metrics consistent with Go client (#129) <Eundoo Song>
+
+
+3.8.0 (2018-03-06)
+------------------
+
+- Replace zipkin.thrift out-of-band span format with jaeger.thrift (#111)
+- Use only `six` for py2/py3 compatibility, drop `future` (#130, #134, #135)
+- Add codec for B3 trace context headers (#112) - thanks @gravelg
+- Increase max tag value length to 1024 and make it configurable (#110)
+- A number of fixes for Python 3.x compatibility
+  - Fix span and sampler tests to work under Py3 (#117)
+  - Fix dependencies for Py3 compatibility (#116)
+  - Fix xrange for Py3 in thrift generated files (#115)
+  - Add python3 compat, hasattr iteritems->itemx (#113) - thanks @kbroughton
+
+
+3.7.1 (2017-12-14)
+------------------
+
+- Encode unicode baggage keys/values to UTF-8 (#109)
+
+
+3.7.0 (2017-12-12)
+------------------
+
+- Change default for one_span_per_rpc to False (#105)
+
+
+3.6.1 (2017-09-26)
+------------------
+
+- Fix bug when creating tracer with tags. (#80)
+
+
+3.6.0 (2017-09-26)
+------------------
+
+- Allow tracer constructor to accept optional tags argument.
+- Support `JAEGER_TAGS` environment variable and config for tracer tags.
+
+
+3.5.0 (2017-07-10)
+------------------
+
+- Add metrics factory and allow tags for metrics [#45]
+- Save baggage in span [#54]
+- Allow to override hostname for jaeger agent [#51]
+
+
+3.4.0 (2017-03-20)
+------------------
+
+- Add adaptive sampler
+- Allow overriding one-span-per-rpc behavior
+- Allow overriding codecs in tracer initialization
+
+
+3.3.1 (2016-10-14)
+------------------
+
+- Replace 0 parentID with None
+
+
+3.3.0 (2016-10-04)
+------------------
+
+- Upgrade to opentracing 1.2 with KV logging.
+
+
+3.2.0 (2016-09-20)
+------------------
+
+- Support debug traces via HTTP header jaeger-debug-id.
+
+
+3.1.0 (2016-09-06)
+------------------
+
+- Report sampling strategy as root span tags `sampler.type` and `sampler.param`. In case of probabilistic sampling (most frequently used strategy), the values would be `probabilistic` and the sampling probability [0 .. 1], respectively.
+- Record host name as `jaeger.hostname` tag on the first-in-process spans (i.e. root spans and rpc-server spans)
+- Record the version of the Jaeger library as `jaeger.version` tag
+
+
+3.0.2 (2016-08-18)
+------------------
+
+- Do not create SpanContext from Zipkin span if trace_id is empty/zero
+
+
+3.0.1 (2016-08-09)
+------------------
+
+- Do not publish crossdock module
+
+
+3.0.0 (2016-08-07)
+------------------
+
+- Upgrade to OpenTracing 1.1
+
+
+2.2.0 (2016-08-02)
+------------------
+
+- Implement Zipkin codec for interop with TChannel
+
+
+2.1.0 (2016-07-19)
+------------------
+
+- Allow passing external IOLoop
+
+
+2.0.0 (2016-07-19)
+------------------
+
+- Remove TChannel dependency
+- Remove dependency on opentracing_instrumentation
+
+
+1.0.1 (2016-07-11)
+------------------
+
+- Downgrade TChannel dependency to >= 0.24
+
+
+1.0.0 (2016-07-10)
+------------------
+
+- Initial open source release.

+ 150 - 0
desktop/core/ext-py/jaeger-client-4.0.0/CONTRIBUTING.md

@@ -0,0 +1,150 @@
+# How to Contribute to Jaeger
+
+We'd love your help!
+
+Jaeger is [Apache 2.0 licensed](LICENSE) and accepts contributions via GitHub
+pull requests. This document outlines some of the conventions on development
+workflow, commit message formatting, contact points and other resources to make
+it easier to get your contribution accepted.
+
+We gratefully welcome improvements to documentation as well as to code.
+
+## Developing
+
+This repository uses [virtualenv](https://pypi.python.org/pypi/virtualenv) and `pip` to manage dependencies.
+To install all dependencies, run:
+
+ 1. `virtualenv env`
+ 2. `source env/bin/activate`
+ 3. `make bootstrap`
+ 4. `make test`
+ 
+### Thrift
+
+Thrift files are generated via `make thrift`. The IDL files are pulled from `jaeger-idl` repo which is defined as a submodule.
+
+### pycurl
+
+On MacOS you may get an error:
+
+```
+ImportError: pycurl: libcurl link-time ssl backend (openssl) is different from compile-time ssl backend (none/other)
+```
+
+It can be fixed with:
+
+```
+pip uninstall pycurl
+export LDFLAGS="-L/usr/local/opt/openssl/lib"
+export CPPFLAGS="-I/usr/local/opt/openssl/include"
+export PYCURL_SSL_LIBRARY=openssl
+pip install --no-cache-dir --compile --ignore-installed --install-option="--with-openssl" --install-option="--openssl-dir=/usr/local/opt/openssl" pycurl
+```
+
+## Making A Change
+
+*Before making any significant changes, please [open an issue](https://github.com/jaegertracing/jaeger-client-python/issues).*
+Discussing your proposed changes ahead of time will make the contribution process smooth for everyone.
+
+Once we've discussed your changes and you've got your code ready, make sure
+that tests are passing (`make test`) and open your pull request. 
+Your pull request is most likely to be accepted if it:
+
+* Includes tests for new functionality.
+* Has a [good commit message](https://chris.beams.io/posts/git-commit/):
+  * Separate subject from body with a blank line
+  * Limit the subject line to 50 characters
+  * Capitalize the subject line
+  * Do not end the subject line with a period
+  * Use the imperative mood in the subject line
+  * Wrap the body at 72 characters
+  * Use the body to explain _what_ and _why_ instead of _how_
+* Each commit is signed by the author ([see below](#sign-your-work)).
+
+## License and Certificate of Origin
+
+By contributing to this project you agree to license your contribution under the terms
+of the [Apache License](LICENSE), and you agree to the [Developer Certificate of
+Origin](https://developercertificate.org/) (DCO). This document was created
+by the Linux Kernel community and is a simple statement that you, as a
+contributor, have the legal right to make the contribution. See the [DCO](DCO)
+file for details.
+
+If you are adding a new file it should have a header like below.
+
+```
+# Copyright (c) 2017, The Jaeger Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+# in compliance with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under the License
+# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+# or implied. See the License for the specific language governing permissions and limitations under
+# the License.
+```
+
+## Sign your work
+
+The sign-off is a simple line at the end of the explanation for the
+patch, which certifies that you wrote it or otherwise have the right to
+pass it on as an open-source patch.  The rules are pretty simple: if you
+can certify the below (from
+[developercertificate.org](http://developercertificate.org/)):
+
+```
+Developer Certificate of Origin
+Version 1.1
+
+Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
+660 York Street, Suite 102,
+San Francisco, CA 94110 USA
+
+Everyone is permitted to copy and distribute verbatim copies of this
+license document, but changing it is not allowed.
+
+
+Developer's Certificate of Origin 1.1
+
+By making a contribution to this project, I certify that:
+
+(a) The contribution was created in whole or in part by me and I
+    have the right to submit it under the open source license
+    indicated in the file; or
+
+(b) The contribution is based upon previous work that, to the best
+    of my knowledge, is covered under an appropriate open source
+    license and I have the right under that license to submit that
+    work with modifications, whether created in whole or in part
+    by me, under the same open source license (unless I am
+    permitted to submit under a different license), as indicated
+    in the file; or
+
+(c) The contribution was provided directly to me by some other
+    person who certified (a), (b) or (c) and I have not modified
+    it.
+
+(d) I understand and agree that this project and the contribution
+    are public and that a record of the contribution (including all
+    personal information I submit with it, including my sign-off) is
+    maintained indefinitely and may be redistributed consistent with
+    this project or the open source license(s) involved.
+```
+
+then you just add a line to every git commit message:
+
+    Signed-off-by: Joe Smith <joe@gmail.com>
+
+using your real name (sorry, no pseudonyms or anonymous contributions.)
+
+You can add the sign off when creating the git commit via `git commit -s`.
+
+If you want this to be automatic you can set up some aliases:
+
+```
+git config --add alias.amend "commit -s --amend"
+git config --add alias.c "commit -s"
+```
+

+ 10 - 0
desktop/core/ext-py/jaeger-client-4.0.0/MANIFEST.in

@@ -0,0 +1,10 @@
+recursive-include jaeger_client *.thrift
+include *.md
+include *.rst
+include *.txt
+include *.yaml
+include Makefile
+recursive-include config *.json
+recursive-include config *.yaml
+recursive-include docs *.rst
+recursive-include tests *.py

+ 107 - 0
desktop/core/ext-py/jaeger-client-4.0.0/Makefile

@@ -0,0 +1,107 @@
+project := jaeger_client
+projects := jaeger_client crossdock
+flake8 := flake8
+COV_DIRS := $(projects:%=--cov %)
+pytest_args := -s --tb short --cov-config .coveragerc $(COV_DIRS) tests
+pytest := py.test $(pytest_args)
+sources := $(shell find $(projects) tests -name '*.py' | grep -v version.py | grep -v thrift_gen)
+
+test_args := --cov-report term-missing --cov-report xml --junitxml junit.xml
+cover_args := --cov-report html
+
+include crossdock/rules.mk
+
+PY_PATH = $(PYTHONPATH):$(PWD)
+
+.DEFAULT_GOAL := test
+
+
+.PHONY: bootstrap
+bootstrap:
+	[ "$$VIRTUAL_ENV" != "" ]
+	rm -rf *.egg-info || true
+	pip install -U 'pip>=9.0'
+	pip install 'setuptools>=20.8.1'
+	pip install -r requirements.txt
+	pip install -r requirements-dev.txt
+	pip install -r requirements-tests.txt
+	pip install virtualenv
+	python setup.py develop
+
+.PHONY: test
+test: clean
+	$(pytest) $(test_args) --benchmark-skip
+
+.PHONY: test_ci
+test_ci: clean test-import test lint
+
+.PHONY: test-import
+test-import:
+	virtualenv import-test
+	import-test/bin/pip install -e .
+	import-test/bin/python -c "import jaeger_client"
+	rm -rf import-test
+
+.PHONY: test-perf
+test-perf: clean
+	$(pytest) $(test_args) --benchmark-only
+
+# --benchmark-histogram --benchmark-min-time=0.001
+
+.PHONY: cover
+cover: clean
+	$(pytest) $(cover_args) --benchmark-skip
+	open htmlcov/index.html
+
+.PHONY: jenkins
+jenkins: bootstrap
+	$(pytest) $(test_args) --benchmark-skip
+
+.PHONY: clean
+clean:
+	@find $(project) "(" -name "*.pyc" -o -name "coverage.xml" -o -name "junit.xml" ")" -delete
+	@find tests "(" -name "*.pyc" -o -name "coverage.xml" -o -name "junit.xml" -o -name __pycache__ ")" -delete
+	@find . "(" -name "*.pyc" -o -name "coverage.xml" -o -name "junit.xml" -o -name __pycache__ ")" -delete
+	@rm -rf jaeger_client.egg-info
+
+.PHONY: lint
+lint:
+	$(flake8) $(projects) tests
+	./scripts/check-license.sh
+
+.PHONY: shell
+shell:
+	ipython
+
+# Generate jaeger thrifts
+THRIFT_GEN_DIR=jaeger_client/thrift_gen
+THRIFT_VER=0.9.3
+THRIFT_IMG=thrift:$(THRIFT_VER)
+THRIFT_PY_ARGS=new_style,tornado
+THRIFT=docker run -v "${PWD}:/data" -u $(shell id -u) $(THRIFT_IMG) thrift
+
+idl-submodule:
+	git submodule init
+	git submodule update
+
+thrift-image:
+	$(THRIFT) -version
+
+.PHONY: thrift
+thrift: idl-submodule thrift-image
+	rm -rf $(THRIFT_GEN_DIR)
+	mkdir $(THRIFT_GEN_DIR)
+	${THRIFT} -o /data --gen py:${THRIFT_PY_ARGS} -out /data/$(THRIFT_GEN_DIR) /data/idl/thrift/jaeger.thrift
+	${THRIFT} -o /data --gen py:${THRIFT_PY_ARGS} -out /data/$(THRIFT_GEN_DIR) /data/idl/thrift/zipkincore.thrift
+	${THRIFT} -o /data --gen py:${THRIFT_PY_ARGS} -out /data/$(THRIFT_GEN_DIR) /data/idl/thrift/agent.thrift
+	${THRIFT} -o /data --gen py:${THRIFT_PY_ARGS} -out /data/$(THRIFT_GEN_DIR) /data/idl/thrift/sampling.thrift
+	rm -rf ${THRIFT_GEN_DIR}/*/*-remote
+	set -e; \
+	for f in $$(find ${THRIFT_GEN_DIR} -iname '*.py'); do \
+	  echo fixing $$f; \
+	  awk -f thrift-gen-fix.awk $$f > tmp; \
+	  mv tmp $$f; \
+	done
+
+update-license:
+	python scripts/updateLicense.py $(sources)

+ 19 - 0
desktop/core/ext-py/jaeger-client-4.0.0/PKG-INFO

@@ -0,0 +1,19 @@
+Metadata-Version: 2.1
+Name: jaeger-client
+Version: 4.0.0
+Summary: Jaeger Python OpenTracing Tracer implementation
+Home-page: https://github.com/jaegertracing/jaeger-client-python
+Author: Yuri Shkuro
+Author-email: ys@uber.com
+License: Apache License 2.0
+Description: UNKNOWN
+Keywords: jaeger,tracing,opentracing
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: Natural Language :: English
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Provides-Extra: tests

+ 192 - 0
desktop/core/ext-py/jaeger-client-4.0.0/README.md

@@ -0,0 +1,192 @@
+[![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] [![PyPI Version][pypi-img]][pypi] [![Python Version][pythonversion-img]][pythonversion] [![FOSSA Status][fossa-img]][fossa]
+
+# Jaeger Bindings for Python OpenTracing API
+
+This is a client-side library that can be used to instrument Python apps
+for distributed trace collection, and to send those traces to Jaeger.
+See the [OpenTracing Python API](https://github.com/opentracing/opentracing-python)
+for additional detail.
+
+## Contributing and Developing
+
+Please see [CONTRIBUTING.md](./CONTRIBUTING.md).
+
+## Installation
+
+```bash
+pip install jaeger-client
+```
+
+## Getting Started
+
+```python
+import logging
+import time
+from jaeger_client import Config
+
+if __name__ == "__main__":
+    log_level = logging.DEBUG
+    logging.getLogger('').handlers = []
+    logging.basicConfig(format='%(asctime)s %(message)s', level=log_level)
+
+    config = Config(
+        config={ # usually read from some yaml config
+            'sampler': {
+                'type': 'const',
+                'param': 1,
+            },
+            'logging': True,
+        },  
+        service_name='your-app-name',
+        validate=True,
+    )
+    # this call also sets opentracing.tracer
+    tracer = config.initialize_tracer()
+
+    with tracer.start_span('TestSpan') as span:
+        span.log_kv({'event': 'test message', 'life': 42})
+
+        with tracer.start_span('ChildSpan', child_of=span) as child_span:
+            span.log_kv({'event': 'down below'})
+
+    time.sleep(2)   # yield to IOLoop to flush the spans - https://github.com/jaegertracing/jaeger-client-python/issues/50
+    tracer.close()  # flush any buffered spans
+```
+
+**NOTE**: If you're using the Jaeger `all-in-one` Docker image (or similar) and want to run Jaeger in a separate container from your app, use the code below to define the host and port that the Jaeger agent is running on. *Note that this is not recommended, as Jaeger sends spans over UDP and UDP does not guarantee delivery.* (See [this thread](https://github.com/jaegertracing/jaeger-client-python/issues/47) for more details.)
+
+```python
+    config = Config(
+        config={ # usually read from some yaml config
+            'sampler': {
+                'type': 'const',
+                'param': 1,
+            },
+            'local_agent': {
+                'reporting_host': 'your-reporting-host',
+                'reporting_port': 'your-reporting-port',
+            },
+            'logging': True,
+        },  
+        service_name='your-app-name',
+        validate=True,
+    )
+```
+
+### Other Instrumentation
+
+The [opentracing-contrib](https://github.com/opentracing-contrib) project has a few modules that provide explicit instrumentation support for popular frameworks like Django and Flask.
+
+At Uber we are mostly using the [opentracing_instrumentation](https://github.com/uber-common/opentracing-python-instrumentation) module that provides:
+  * explicit instrumentation for HTTP servers, and
+  * implicit (monkey-patched) instrumentation for several popular libraries like `urllib2`, `redis`, `requests`, some SQL clients, etc.
+
+## Initialization & Configuration
+
+Note: do not initialize the tracer during import, it may cause a deadlock (see issues #31, #60).
+Instead define a function that returns a tracer (see example below) and call that function explicitly
+after all the imports are done.
+
+### Production
+
+The recommended way to initialize the tracer for production use:
+
+```python
+from jaeger_client import Config
+
+def init_jaeger_tracer(service_name='your-app-name'):
+    config = Config(config={}, service_name=service_name, validate=True)
+    return config.initialize_tracer()
+```
+
+Note that the call `initialize_tracer()` also sets the `opentracing.tracer` global variable.
+If you need to create additional tracers (e.g., to create spans on the client side for remote services that are not instrumented), use the `new_tracer()` method.
+
+#### Prometheus metrics
+
+This module brings a [Prometheus](https://github.com/prometheus/client_python) integration to the internal Jaeger metrics.  
+The way to initialize the tracer with Prometheus metrics:
+
+```python
+from jaeger_client.metrics.prometheus import PrometheusMetricsFactory
+
+config = Config(
+        config={},
+        service_name='your-app-name',
+        validate=True,
+        metrics_factory=PrometheusMetricsFactory(namespace='your-app-name')
+)
+tracer = config.initialize_tracer()
+```
+
+### Development
+
+For development, some parameters can be passed via `config` dictionary, as in the Getting Started example above. For more details please see the [Config class](jaeger_client/config.py).
+
+### WSGI
+
+Applications running under WSGI usually fork multiple sub-processes to handle individual requests.
+When Jaeger tracer is initialized, it may start a new background thread. If the process later forks,
+it might cause issues or hang the application (due to exclusive lock on the interpreter).
+Therefore, it is recommended that the tracer is not initialized until after the child processes
+are forked. Depending on the WSGI framework you might be able to use `@postfork` decorator
+to delay tracer initialization (see also issues #31, #60).
+
+## Debug Traces (Forced Sampling)
+
+### Programmatically
+
+The OpenTracing API defines a `sampling.priority` standard tag that
+can be used to affect the sampling of a span and its children:
+
+```python
+from opentracing.ext import tags as ext_tags
+
+span.set_tag(ext_tags.SAMPLING_PRIORITY, 1)
+```
+
+### Via HTTP Headers
+
+Jaeger Tracer also understands a special HTTP Header `jaeger-debug-id`,
+which can be set in the incoming request, e.g.
+
+```sh
+curl -H "jaeger-debug-id: some-correlation-id" http://myhost.com
+```
+
+When Jaeger sees this header in the request that otherwise has no
+tracing context, it ensures that the new trace started for this
+request will be sampled in the "debug" mode (meaning it should survive
+all downsampling that might happen in the collection pipeline), and
+the root span will have a tag as if this statement was executed:
+
+```python
+span.set_tag('jaeger-debug-id', 'some-correlation-id')
+```
+
+This allows using Jaeger UI to find the trace by this tag.
+
+## Zipkin Compatibility
+
+To use this library directly with other Zipkin libraries & backend,
+you can provide the configuration property `propagation: 'b3'` and the
+`X-B3-*` HTTP headers will be supported.
+
+The B3 codec assumes it will receive lowercase HTTP headers, as this seems
+to be the standard in the popular frameworks like Flask and Django.
+Please make sure your framework does the same.
+
+## License
+
+[Apache 2.0 License](./LICENSE).
+
+[ci-img]: https://travis-ci.org/jaegertracing/jaeger-client-python.svg?branch=master
+[ci]: https://travis-ci.org/jaegertracing/jaeger-client-python
+[cov-img]: https://codecov.io/gh/jaegertracing/jaeger-client-python/branch/master/graph/badge.svg
+[cov]: https://codecov.io/gh/jaegertracing/jaeger-client-python
+[pypi-img]: https://badge.fury.io/py/jaeger-client.svg
+[pypi]: https://badge.fury.io/py/jaeger-client
+[pythonversion-img]: https://img.shields.io/pypi/pyversions/jaeger-client.svg
+[pythonversion]: https://pypi.org/project/jaeger-client
+[fossa-img]: https://app.fossa.io/api/projects/git%2Bgithub.com%2Fjaegertracing%2Fjaeger-client-python.svg?type=shield
+[fossa]: https://app.fossa.io/projects/git%2Bgithub.com%2Fjaegertracing%2Fjaeger-client-python?ref=badge_shield

+ 22 - 0
desktop/core/ext-py/jaeger-client-4.0.0/RELEASE.md

@@ -0,0 +1,22 @@
+# Release Process
+
+Declaring formal releases requires peer review.
+
+* A reviewer of a pull request should recommend a new version number (patch, minor or major).
+* Once your change is merged feel free to bump the version as recommended by the reviewer.
+* A new version number should not be cut without peer review unless done by the project maintainer.
+
+## Publishing a new version
+
+Before new release, add a summary of changes since last version to (CHANGELOG.rst)[./CHANGELOG.rst].
+
+```
+pip install zest.releaser[recommended]
+prerelease
+release
+git push origin master --follow-tags
+python setup.py sdist upload -r pypi
+postrelease
+git push
+```
+

+ 13 - 0
desktop/core/ext-py/jaeger-client-4.0.0/crossdock/server/__init__.py

@@ -0,0 +1,13 @@
+# Copyright (c) 2016 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.

+ 15 - 0
desktop/core/ext-py/jaeger-client-4.0.0/crossdock/server/constants.py

@@ -0,0 +1,15 @@
+# Copyright (c) 2016 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+baggage_key = 'crossdock-baggage-key'

+ 150 - 0
desktop/core/ext-py/jaeger-client-4.0.0/crossdock/server/endtoend.py

@@ -0,0 +1,150 @@
+# Copyright (c) 2016-2018 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import tornado.web
+import logging
+import json
+import os
+
+from jaeger_client.local_agent_net import LocalAgentSender
+from jaeger_client.config import (
+    Config,
+    DEFAULT_SAMPLING_PORT,
+    DEFAULT_REPORTING_PORT,
+)
+from jaeger_client.constants import (
+    SAMPLER_TYPE_CONST,
+    SAMPLER_TYPE_REMOTE,
+)
+from jaeger_client.sampler import RemoteControlledSampler, ConstSampler
+from jaeger_client.reporter import Reporter
+from jaeger_client.throttler import RemoteThrottler
+from jaeger_client.tracer import Tracer
+
+config = {
+    'service_name': 'crossdock-python',
+    'enabled': True,
+    'sampler': {
+        'type': 'probabilistic',
+        'param': 1,
+    },
+    'reporter_flush_interval': 1,
+    'sampling_refresh_interval': 5,
+}
+
+
+class EndToEndHandler(object):
+    """
+    Handler that creates traces from a http request.
+
+    json: {
+        "type": "remote"
+        "operation": "operationName",
+        "count": 2,
+        "tags": {
+            "key": "value"
+        }
+    }
+
+    Given the above json payload, the handler will use a tracer with the RemoteControlledSampler
+    to create 2 traces for the "operationName" operation with the tags: {"key":"value"}. These
+    traces are reported to the agent with the hostname "test_driver".
+    """
+
+    def __init__(self):
+        cfg = Config(config)
+        init_sampler = cfg.sampler
+        channel = self.local_agent_sender
+
+        reporter = Reporter(channel=channel,
+                            flush_interval=cfg.reporter_flush_interval)
+
+        remote_sampler = RemoteControlledSampler(
+            channel=channel,
+            service_name=cfg.service_name,
+            sampling_refresh_interval=cfg.sampling_refresh_interval,
+            init_sampler=init_sampler)
+
+        throttler = RemoteThrottler(channel, cfg.service_name)
+
+        remote_tracer = Tracer(
+            service_name=cfg.service_name,
+            reporter=reporter,
+            sampler=remote_sampler,
+            throttler=throttler)
+
+        const_tracer = Tracer(
+            service_name=cfg.service_name,
+            reporter=reporter,
+            sampler=ConstSampler(decision=True),
+            throttler=throttler
+        )
+
+        self._tracers = {
+            SAMPLER_TYPE_CONST: const_tracer,
+            SAMPLER_TYPE_REMOTE: remote_tracer
+        }
+
+    @property
+    def tracers(self):
+        return self._tracers
+
+    @tracers.setter
+    def tracers(self, tracers):
+        self._tracers = tracers
+
+    @property
+    def local_agent_sender(self):
+        host, port = _determine_host_port()
+        return LocalAgentSender(
+            host=host,
+            sampling_port=DEFAULT_SAMPLING_PORT,
+            reporting_port=port,
+            throttling_port=DEFAULT_SAMPLING_PORT,
+        )
+
+    @tornado.gen.coroutine
+    def generate_traces(self, request, response_writer):
+        req = json.loads(request.body)
+        sampler_type = req.get('type', 'remote')
+        tracer = self.tracers[sampler_type]
+        for _ in range(req.get('count', 0)):
+            span = tracer.start_span(req['operation'])
+            for k, v in req.get('tags', {}).iteritems():
+                span.set_tag(k, v)
+            span.finish()
+        response_writer.finish()
+
+
+def _determine_host_port():
+    host_port = os.environ.get('AGENT_HOST_PORT', None)
+    if host_port:
+        host, port = _parse_host_port(host_port,
+                                      'jaeger-agent',
+                                      DEFAULT_REPORTING_PORT)
+    else:
+        host, port = 'jaeger-agent', DEFAULT_REPORTING_PORT
+    return host, port
+
+
+def _parse_host_port(host_port, default_host, default_port):
+    try:
+        host, port_str = host_port.split(':')
+        port = int(port_str)
+        return host, port
+    except ValueError:
+        logging.getLogger().error(
+            'Invalid host port (%s), using default host port (%s:%d)',
+            host_port, default_host, default_port)
+        return default_host, default_port

+ 122 - 0
desktop/core/ext-py/jaeger-client-4.0.0/crossdock/server/serializer.py

@@ -0,0 +1,122 @@
+# Copyright (c) 2016 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import json
+import logging
+
+from crossdock.thrift_gen.tracetest.ttypes import JoinTraceRequest, StartTraceRequest, \
+    Downstream, Transport, TraceResponse, ObservedSpan
+
+
+#
+# Serializers for the downstream calls
+#
+def set_downstream_object_values(downstream_object, json_obj):
+    return set_traced_service_object_values(downstream_object, json_obj, downstream_from_struct)
+
+
+def join_trace_request_from_json(json_request):
+    request = JoinTraceRequest()
+    set_downstream_object_values(request, json.loads(json_request))
+    return request
+
+
+def start_trace_request_from_json(json_request):
+    request = StartTraceRequest()
+    set_downstream_object_values(request, json.loads(json_request))
+    return request
+
+
+def downstream_from_struct(json_obj):
+    downstream = Downstream()
+    set_downstream_object_values(downstream, json_obj)
+    return downstream
+
+
+def join_trace_request_to_json(downstream, server_role):
+    req = {}
+    if downstream is not None:
+        req['downstream'] = traced_service_object_to_json(downstream)
+    if server_role is not None:
+        req['serverRole'] = str(server_role)
+    return json.dumps(req)
+
+#
+# Serializers for the upstream responses
+#
+
+
+def set_upstream_object_values(obj, json_obj):
+    return set_traced_service_object_values(obj, json_obj, traceresponse_from_struct)
+
+
+def observed_span_from_struct(json_obj):
+    os = ObservedSpan()
+    set_upstream_object_values(os, json_obj)
+    return os
+
+
+def traceresponse_from_struct(json_obj):
+    tr = TraceResponse()
+    set_upstream_object_values(tr, json_obj)
+    return tr
+
+
+def traceresponse_from_json(json_str):
+    try:
+        return traceresponse_from_struct(json.loads(json_str))
+    except:  # noqa: E722
+        logging.exception('Failed to parse JSON')
+        raise
+
+# Generic
+
+
+def class_keys(obj):
+    return [a for a in dir(obj) if not a.startswith('__') and not
+            callable(getattr(obj, a)) and not
+            a == 'type_spec']
+
+
+def traced_service_object_to_json(obj):
+    json_response = {}
+    for k in class_keys(obj):
+        if k == 'downstream':
+            if obj.downstream is not None:
+                json_response['downstream'] = traced_service_object_to_json(obj.downstream)
+        elif k == 'transport':
+            if obj.transport is not None:
+                json_response['transport'] = Transport._VALUES_TO_NAMES[obj.transport]
+        elif k == 'span':
+            if obj.span is not None:
+                json_response['span'] = traced_service_object_to_json(obj.span)
+        else:
+            json_response[k] = getattr(obj, k)
+    return json_response
+
+
+def set_traced_service_object_values(obj, values, downstream_func):
+    for k in values.iterkeys():
+        if hasattr(obj, k):
+            if k == 'downstream':
+                if values[k] is not None:
+                    obj.downstream = downstream_func(values[k])
+            elif k == 'transport':
+                if values[k] is not None:
+                    obj.transport = Transport._NAMES_TO_VALUES[values[k]]
+            elif k == 'span':
+                if values[k] is not None:
+                    obj.span = observed_span_from_struct(values[k])
+            else:
+                setattr(obj, k, values[k])

+ 226 - 0
desktop/core/ext-py/jaeger-client-4.0.0/crossdock/server/server.py

@@ -0,0 +1,226 @@
+# Copyright (c) 2016 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+
+import tornado.web
+import opentracing
+import tornado.ioloop
+import tornado.httpclient
+from tornado.web import asynchronous
+from jaeger_client import Tracer, ConstSampler
+from jaeger_client.reporter import NullReporter
+import crossdock.server.constants as constants
+import crossdock.server.serializer as serializer
+from crossdock.server.endtoend import EndToEndHandler
+from opentracing_instrumentation import http_client, http_server, get_current_span, request_context
+from opentracing_instrumentation.client_hooks import tornado_http
+import opentracing.ext.tags as ext_tags
+from crossdock.thrift_gen.tracetest.ttypes import ObservedSpan, TraceResponse, Transport, \
+    JoinTraceRequest
+
+from tchannel import TChannel, thrift
+from crossdock.server.thriftrw_serializer import trace_response_to_thriftrw, \
+    join_trace_request_to_thriftrw
+
+DefaultClientPortHTTP = 8080
+DefaultServerPortHTTP = 8081
+DefaultServerPortTChannel = 8082
+
+
+tracer = Tracer(
+    service_name='python',
+    reporter=NullReporter(),
+    sampler=ConstSampler(decision=True))
+opentracing.tracer = tracer
+
+
+idl_path = 'idl/thrift/crossdock/tracetest.thrift'
+thrift_services = {}
+
+
+def get_thrift_service(service_name):
+    if service_name in thrift_services:
+        return thrift_services[service_name]
+    thrift_service = thrift.load(path=idl_path, service=service_name)
+    thrift_services[service_name] = thrift_service
+    return thrift_service
+
+
+def serve():
+    """main entry point"""
+    logging.getLogger().setLevel(logging.DEBUG)
+    logging.info('Python Tornado Crossdock Server Running ...')
+    server = Server(DefaultServerPortTChannel)
+    endtoend_handler = EndToEndHandler()
+    app = make_app(server, endtoend_handler)
+    app.listen(DefaultClientPortHTTP)
+    app.listen(DefaultServerPortHTTP)
+    server.tchannel.listen()
+    tornado.ioloop.IOLoop.current().start()
+
+
+# Tornado Stuff
+class MainHandler(tornado.web.RequestHandler):
+    def initialize(self, server=None, method=None):
+        self.server = server
+        self.method = method
+
+    @asynchronous
+    def get(self):
+        if self.server and self.method:
+            self.method(self.server, self.request, self)
+        else:
+            self.finish()
+
+    @asynchronous
+    def post(self):
+        if self.server and self.method:
+            self.method(self.server, self.request, self)
+        else:
+            self.finish()
+
+    def head(self):
+        pass
+
+
+def make_app(server, endtoend_handler):
+    return tornado.web.Application(
+        [
+            (r'/', MainHandler),
+            (r'/start_trace', MainHandler, (dict(server=server, method=Server.start_trace))),
+            (r'/join_trace', MainHandler, (dict(server=server, method=Server.join_trace))),
+            (r'/create_traces', MainHandler, (dict(server=endtoend_handler,
+                                                   method=EndToEndHandler.generate_traces))),
+        ], debug=True, autoreload=False)
+
+
+# HTTP Tracing Stuff
+class UnknownTransportException(Exception):
+    pass
+
+
+def get_observed_span(span):
+    return ObservedSpan(
+        traceId='%x' % span.trace_id,
+        sampled=span.is_sampled(),
+        baggage=span.get_baggage_item(constants.baggage_key)
+    )
+
+
+class Server(object):
+    def __init__(self, port):
+        self.tracer = opentracing.tracer
+        self.tchannel = self.make_tchannel(port)
+
+    def make_tchannel(self, port):
+        tchannel = TChannel('python', hostport='localhost:%d' % port, trace=True)
+
+        service = get_thrift_service(service_name='python')
+
+        @tchannel.thrift.register(service.TracedService, method='joinTrace')
+        @tornado.gen.coroutine
+        def join_trace(request):
+            join_trace_request = request.body.request or None
+            logging.info('TChannel join_trace request: %s', join_trace_request)
+            response = yield self.prepare_response(get_current_span(),
+                                                   join_trace_request.downstream)
+            logging.info('TChannel join_trace response: %s', response)
+            raise tornado.gen.Return(trace_response_to_thriftrw(service, response))
+
+        return tchannel
+
+    @tornado.gen.coroutine
+    def start_trace(self, request, response_writer):
+        start_trace_req = serializer.start_trace_request_from_json(request.body)
+        logging.info('HTTP start_trace request: %s', start_trace_req)
+
+        def update_span(span):
+            span.set_baggage_item(constants.baggage_key, start_trace_req.baggage)
+            span.set_tag(ext_tags.SAMPLING_PRIORITY, start_trace_req.sampled)
+
+        response = yield self.prepare_response(self.get_span(request, update_span),
+                                               start_trace_req.downstream)
+        logging.info('HTTP start_trace response: %s', response)
+        response_writer.write(serializer.traced_service_object_to_json(response))
+        response_writer.finish()
+
+    @tornado.gen.coroutine
+    def join_trace(self, request, response_writer):
+        join_trace_request = serializer.join_trace_request_from_json(request.body)
+        logging.info('HTTP join_trace request: %s', join_trace_request)
+
+        response = yield self.prepare_response(self.get_span(request, None),
+                                               join_trace_request.downstream)
+        logging.info('HTTP join_trace response: %s', response)
+        response_writer.write(serializer.traced_service_object_to_json(response))
+        response_writer.finish()
+
+    def get_span(self, http_request, update_span_func):
+        span = http_server.before_request(http_server.TornadoRequestWrapper(request=http_request),
+                                          self.tracer)
+        if update_span_func:
+            update_span_func(span)
+
+        return span
+
+    @tornado.gen.coroutine
+    def prepare_response(self, span, downstream):
+        observed_span = get_observed_span(span)
+        trace_response = TraceResponse(span=observed_span, notImplementedError='')
+
+        if downstream:
+            with request_context.span_in_stack_context(span):
+                future = self.call_downstream(span, downstream)
+            downstream_trace_resp = yield future
+            trace_response.downstream = downstream_trace_resp
+
+        raise tornado.gen.Return(trace_response)
+
+    @tornado.gen.coroutine
+    def call_downstream(self, span, downstream):
+        if downstream.transport == Transport.HTTP:
+            downstream_trace_resp = yield self.call_downstream_http(span, downstream)
+        elif downstream.transport == Transport.TCHANNEL:
+            downstream_trace_resp = yield self.call_downstream_tchannel(downstream)
+        else:
+            raise UnknownTransportException('%s' % downstream.transport)
+        raise tornado.gen.Return(downstream_trace_resp)
+
+    @tornado.gen.coroutine
+    def call_downstream_http(self, span, downstream):
+        url = 'http://%s:%s/join_trace' % (downstream.host, downstream.port)
+        body = serializer.join_trace_request_to_json(downstream.downstream, downstream.serverRole)
+
+        req = tornado.httpclient.HTTPRequest(url=url, method='POST',
+                                             headers={'Content-Type': 'application/json'},
+                                             body=body)
+        http_client.before_http_request(tornado_http.TornadoRequestWrapper(request=req),
+                                        lambda: span)
+        client = tornado.httpclient.AsyncHTTPClient()
+        http_result = yield client.fetch(req)
+
+        raise tornado.gen.Return(serializer.traceresponse_from_json(http_result.body))
+
+    @tornado.gen.coroutine
+    def call_downstream_tchannel(self, downstream):
+        downstream_service = get_thrift_service(downstream.serviceName)
+
+        jtr = JoinTraceRequest(downstream.serverRole, downstream.downstream)
+        jtr = join_trace_request_to_thriftrw(downstream_service, jtr)
+
+        hostport = '%s:%s' % (downstream.host, downstream.port)
+        thrift_result = yield self.tchannel.thrift(downstream_service.TracedService.joinTrace(jtr),
+                                                   hostport=hostport)
+        raise tornado.gen.Return(thrift_result.body)

+ 42 - 0
desktop/core/ext-py/jaeger-client-4.0.0/crossdock/server/thriftrw_serializer.py

@@ -0,0 +1,42 @@
+# Copyright (c) 2016 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+def trace_response_to_thriftrw(service, tr):
+    if tr is None:
+        return None
+    return service.TraceResponse(span=observed_span_to_thriftrw(service, tr.span),
+                                 downstream=trace_response_to_thriftrw(service, tr.downstream),
+                                 notImplementedError=tr.notImplementedError or '')
+
+
+def downstream_to_thriftrw(service, downstream):
+    if downstream is None:
+        return None
+    return service.Downstream(downstream.serviceName,
+                              downstream.serverRole,
+                              downstream.host,
+                              downstream.port,
+                              downstream.transport,
+                              downstream.downstream)
+
+
+def join_trace_request_to_thriftrw(service, jtr):
+    return service.JoinTraceRequest(jtr.serverRole, downstream_to_thriftrw(service, jtr.downstream))
+
+
+def observed_span_to_thriftrw(service, observed_span):
+    return service.ObservedSpan(traceId=observed_span.traceId,
+                                sampled=observed_span.sampled,
+                                baggage=observed_span.baggage)

+ 1 - 0
desktop/core/ext-py/jaeger-client-4.0.0/crossdock/thrift_gen/__init__.py

@@ -0,0 +1 @@
+__all__ = ["tracetest"]

+ 448 - 0
desktop/core/ext-py/jaeger-client-4.0.0/crossdock/thrift_gen/tracetest/TracedService.py

@@ -0,0 +1,448 @@
+#
+# Autogenerated by Thrift Compiler (0.9.3)
+#
+# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+#
+#  options string: py:new_style,tornado
+#
+
+from thrift.Thrift import TType, TMessageType, TException, TApplicationException
+import logging
+from .ttypes import *
+from thrift.Thrift import TProcessor
+from thrift.transport import TTransport
+from thrift.protocol import TBinaryProtocol, TProtocol
+try:
+  from thrift.protocol import fastbinary
+except:
+  fastbinary = None
+
+from tornado import gen
+from tornado import concurrent
+from thrift.transport import TTransport
+
+class Iface(object):
+  def startTrace(self, request):
+    """
+    Parameters:
+     - request
+    """
+    pass
+
+  def joinTrace(self, request):
+    """
+    Parameters:
+     - request
+    """
+    pass
+
+
+class Client(Iface):
+  def __init__(self, transport, iprot_factory, oprot_factory=None):
+    self._transport = transport
+    self._iprot_factory = iprot_factory
+    self._oprot_factory = (oprot_factory if oprot_factory is not None
+                           else iprot_factory)
+    self._seqid = 0
+    self._reqs = {}
+    self._transport.io_loop.spawn_callback(self._start_receiving)
+
+  @gen.engine
+  def _start_receiving(self):
+    while True:
+      try:
+        frame = yield self._transport.readFrame()
+      except TTransport.TTransportException as e:
+        for future in self._reqs.itervalues():
+          future.set_exception(e)
+        self._reqs = {}
+        return
+      tr = TTransport.TMemoryBuffer(frame)
+      iprot = self._iprot_factory.getProtocol(tr)
+      (fname, mtype, rseqid) = iprot.readMessageBegin()
+      future = self._reqs.pop(rseqid, None)
+      if not future:
+        # future has already been discarded
+        continue
+      method = getattr(self, 'recv_' + fname)
+      try:
+        result = method(iprot, mtype, rseqid)
+      except Exception as e:
+        future.set_exception(e)
+      else:
+        future.set_result(result)
+
+  def startTrace(self, request):
+    """
+    Parameters:
+     - request
+    """
+    self._seqid += 1
+    future = self._reqs[self._seqid] = concurrent.Future()
+    self.send_startTrace(request)
+    return future
+
+  def send_startTrace(self, request):
+    oprot = self._oprot_factory.getProtocol(self._transport)
+    oprot.writeMessageBegin('startTrace', TMessageType.CALL, self._seqid)
+    args = startTrace_args()
+    args.request = request
+    args.write(oprot)
+    oprot.writeMessageEnd()
+    oprot.trans.flush()
+
+  def recv_startTrace(self, iprot, mtype, rseqid):
+    if mtype == TMessageType.EXCEPTION:
+      x = TApplicationException()
+      x.read(iprot)
+      iprot.readMessageEnd()
+      raise x
+    result = startTrace_result()
+    result.read(iprot)
+    iprot.readMessageEnd()
+    if result.success is not None:
+      return result.success
+    raise TApplicationException(TApplicationException.MISSING_RESULT, "startTrace failed: unknown result")
+
+  def joinTrace(self, request):
+    """
+    Parameters:
+     - request
+    """
+    self._seqid += 1
+    future = self._reqs[self._seqid] = concurrent.Future()
+    self.send_joinTrace(request)
+    return future
+
+  def send_joinTrace(self, request):
+    oprot = self._oprot_factory.getProtocol(self._transport)
+    oprot.writeMessageBegin('joinTrace', TMessageType.CALL, self._seqid)
+    args = joinTrace_args()
+    args.request = request
+    args.write(oprot)
+    oprot.writeMessageEnd()
+    oprot.trans.flush()
+
+  def recv_joinTrace(self, iprot, mtype, rseqid):
+    if mtype == TMessageType.EXCEPTION:
+      x = TApplicationException()
+      x.read(iprot)
+      iprot.readMessageEnd()
+      raise x
+    result = joinTrace_result()
+    result.read(iprot)
+    iprot.readMessageEnd()
+    if result.success is not None:
+      return result.success
+    raise TApplicationException(TApplicationException.MISSING_RESULT, "joinTrace failed: unknown result")
+
+
+class Processor(Iface, TProcessor):
+  def __init__(self, handler):
+    self._handler = handler
+    self._processMap = {}
+    self._processMap["startTrace"] = Processor.process_startTrace
+    self._processMap["joinTrace"] = Processor.process_joinTrace
+
+  def process(self, iprot, oprot):
+    (name, type, seqid) = iprot.readMessageBegin()
+    if name not in self._processMap:
+      iprot.skip(TType.STRUCT)
+      iprot.readMessageEnd()
+      x = TApplicationException(TApplicationException.UNKNOWN_METHOD, 'Unknown function %s' % (name))
+      oprot.writeMessageBegin(name, TMessageType.EXCEPTION, seqid)
+      x.write(oprot)
+      oprot.writeMessageEnd()
+      oprot.trans.flush()
+      return
+    else:
+      return self._processMap[name](self, seqid, iprot, oprot)
+
+  @gen.coroutine
+  def process_startTrace(self, seqid, iprot, oprot):
+    args = startTrace_args()
+    args.read(iprot)
+    iprot.readMessageEnd()
+    result = startTrace_result()
+    result.success = yield gen.maybe_future(self._handler.startTrace(args.request))
+    oprot.writeMessageBegin("startTrace", TMessageType.REPLY, seqid)
+    result.write(oprot)
+    oprot.writeMessageEnd()
+    oprot.trans.flush()
+
+  @gen.coroutine
+  def process_joinTrace(self, seqid, iprot, oprot):
+    args = joinTrace_args()
+    args.read(iprot)
+    iprot.readMessageEnd()
+    result = joinTrace_result()
+    result.success = yield gen.maybe_future(self._handler.joinTrace(args.request))
+    oprot.writeMessageBegin("joinTrace", TMessageType.REPLY, seqid)
+    result.write(oprot)
+    oprot.writeMessageEnd()
+    oprot.trans.flush()
+
+
+# HELPER FUNCTIONS AND STRUCTURES
+
+class startTrace_args(object):
+  """
+  Attributes:
+   - request
+  """
+
+  thrift_spec = (
+    None, # 0
+    (1, TType.STRUCT, 'request', (StartTraceRequest, StartTraceRequest.thrift_spec), None, ), # 1
+  )
+
+  def __init__(self, request=None,):
+    self.request = request
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 1:
+        if ftype == TType.STRUCT:
+          self.request = StartTraceRequest()
+          self.request.read(iprot)
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('startTrace_args')
+    if self.request is not None:
+      oprot.writeFieldBegin('request', TType.STRUCT, 1)
+      self.request.write(oprot)
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.request)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in self.__dict__.iteritems()]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)
+
+class startTrace_result(object):
+  """
+  Attributes:
+   - success
+  """
+
+  thrift_spec = (
+    (0, TType.STRUCT, 'success', (TraceResponse, TraceResponse.thrift_spec), None, ), # 0
+  )
+
+  def __init__(self, success=None,):
+    self.success = success
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 0:
+        if ftype == TType.STRUCT:
+          self.success = TraceResponse()
+          self.success.read(iprot)
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('startTrace_result')
+    if self.success is not None:
+      oprot.writeFieldBegin('success', TType.STRUCT, 0)
+      self.success.write(oprot)
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.success)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in self.__dict__.iteritems()]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)
+
+class joinTrace_args(object):
+  """
+  Attributes:
+   - request
+  """
+
+  thrift_spec = (
+    None, # 0
+    (1, TType.STRUCT, 'request', (JoinTraceRequest, JoinTraceRequest.thrift_spec), None, ), # 1
+  )
+
+  def __init__(self, request=None,):
+    self.request = request
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 1:
+        if ftype == TType.STRUCT:
+          self.request = JoinTraceRequest()
+          self.request.read(iprot)
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('joinTrace_args')
+    if self.request is not None:
+      oprot.writeFieldBegin('request', TType.STRUCT, 1)
+      self.request.write(oprot)
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.request)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in self.__dict__.iteritems()]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)
+
+class joinTrace_result(object):
+  """
+  Attributes:
+   - success
+  """
+
+  thrift_spec = (
+    (0, TType.STRUCT, 'success', (TraceResponse, TraceResponse.thrift_spec), None, ), # 0
+  )
+
+  def __init__(self, success=None,):
+    self.success = success
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 0:
+        if ftype == TType.STRUCT:
+          self.success = TraceResponse()
+          self.success.read(iprot)
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('joinTrace_result')
+    if self.success is not None:
+      oprot.writeFieldBegin('success', TType.STRUCT, 0)
+      self.success.write(oprot)
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.success)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in self.__dict__.iteritems()]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)

+ 1 - 0
desktop/core/ext-py/jaeger-client-4.0.0/crossdock/thrift_gen/tracetest/__init__.py

@@ -0,0 +1 @@
+__all__ = ['ttypes', 'constants', 'TracedService']

+ 11 - 0
desktop/core/ext-py/jaeger-client-4.0.0/crossdock/thrift_gen/tracetest/constants.py

@@ -0,0 +1,11 @@
+#
+# Autogenerated by Thrift Compiler (0.9.3)
+#
+# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+#
+#  options string: py:new_style,tornado
+#
+
+from thrift.Thrift import TType, TMessageType, TException, TApplicationException
+from .ttypes import *
+

+ 564 - 0
desktop/core/ext-py/jaeger-client-4.0.0/crossdock/thrift_gen/tracetest/ttypes.py

@@ -0,0 +1,564 @@
+#
+# Autogenerated by Thrift Compiler (0.9.3)
+#
+# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+#
+#  options string: py:new_style,tornado
+#
+
+from thrift.Thrift import TType, TMessageType, TException, TApplicationException
+
+from thrift.transport import TTransport
+from thrift.protocol import TBinaryProtocol, TProtocol
+try:
+  from thrift.protocol import fastbinary
+except:
+  fastbinary = None
+
+
+class Transport(object):
+  HTTP = 0
+  TCHANNEL = 1
+
+  _VALUES_TO_NAMES = {
+    0: "HTTP",
+    1: "TCHANNEL",
+  }
+
+  _NAMES_TO_VALUES = {
+    "HTTP": 0,
+    "TCHANNEL": 1,
+  }
+
+
+class Downstream(object):
+  """
+  Attributes:
+   - serviceName
+   - serverRole
+   - host
+   - port
+   - transport
+   - downstream
+  """
+
+  #thrift_spec = (
+    #None, # 0
+    #(1, TType.STRING, 'serviceName', None, None, ), # 1
+    #(2, TType.STRING, 'serverRole', None, None, ), # 2
+    #(3, TType.STRING, 'host', None, None, ), # 3
+    #(4, TType.STRING, 'port', None, None, ), # 4
+    #(5, TType.I32, 'transport', None, None, ), # 5
+    #(6, TType.STRUCT, 'downstream', (Downstream, Downstream.thrift_spec), None, ), # 6
+  #)
+
+  def __init__(self, serviceName=None, serverRole=None, host=None, port=None, transport=None, downstream=None,):
+    self.serviceName = serviceName
+    self.serverRole = serverRole
+    self.host = host
+    self.port = port
+    self.transport = transport
+    self.downstream = downstream
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 1:
+        if ftype == TType.STRING:
+          self.serviceName = iprot.readString()
+        else:
+          iprot.skip(ftype)
+      elif fid == 2:
+        if ftype == TType.STRING:
+          self.serverRole = iprot.readString()
+        else:
+          iprot.skip(ftype)
+      elif fid == 3:
+        if ftype == TType.STRING:
+          self.host = iprot.readString()
+        else:
+          iprot.skip(ftype)
+      elif fid == 4:
+        if ftype == TType.STRING:
+          self.port = iprot.readString()
+        else:
+          iprot.skip(ftype)
+      elif fid == 5:
+        if ftype == TType.I32:
+          self.transport = iprot.readI32()
+        else:
+          iprot.skip(ftype)
+      elif fid == 6:
+        if ftype == TType.STRUCT:
+          self.downstream = Downstream()
+          self.downstream.read(iprot)
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('Downstream')
+    if self.serviceName is not None:
+      oprot.writeFieldBegin('serviceName', TType.STRING, 1)
+      oprot.writeString(self.serviceName)
+      oprot.writeFieldEnd()
+    if self.serverRole is not None:
+      oprot.writeFieldBegin('serverRole', TType.STRING, 2)
+      oprot.writeString(self.serverRole)
+      oprot.writeFieldEnd()
+    if self.host is not None:
+      oprot.writeFieldBegin('host', TType.STRING, 3)
+      oprot.writeString(self.host)
+      oprot.writeFieldEnd()
+    if self.port is not None:
+      oprot.writeFieldBegin('port', TType.STRING, 4)
+      oprot.writeString(self.port)
+      oprot.writeFieldEnd()
+    if self.transport is not None:
+      oprot.writeFieldBegin('transport', TType.I32, 5)
+      oprot.writeI32(self.transport)
+      oprot.writeFieldEnd()
+    if self.downstream is not None:
+      oprot.writeFieldBegin('downstream', TType.STRUCT, 6)
+      self.downstream.write(oprot)
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    if self.serviceName is None:
+      raise TProtocol.TProtocolException(message='Required field serviceName is unset!')
+    if self.serverRole is None:
+      raise TProtocol.TProtocolException(message='Required field serverRole is unset!')
+    if self.host is None:
+      raise TProtocol.TProtocolException(message='Required field host is unset!')
+    if self.port is None:
+      raise TProtocol.TProtocolException(message='Required field port is unset!')
+    if self.transport is None:
+      raise TProtocol.TProtocolException(message='Required field transport is unset!')
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.serviceName)
+    value = (value * 31) ^ hash(self.serverRole)
+    value = (value * 31) ^ hash(self.host)
+    value = (value * 31) ^ hash(self.port)
+    value = (value * 31) ^ hash(self.transport)
+    value = (value * 31) ^ hash(self.downstream)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in self.__dict__.iteritems()]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)
+
+class StartTraceRequest(object):
+  """
+  Attributes:
+   - serverRole
+   - sampled
+   - baggage
+   - downstream
+  """
+
+  #thrift_spec = (
+    #None, # 0
+    #(1, TType.STRING, 'serverRole', None, None, ), # 1
+    #(2, TType.BOOL, 'sampled', None, None, ), # 2
+    #(3, TType.STRING, 'baggage', None, None, ), # 3
+    #(4, TType.STRUCT, 'downstream', (Downstream, Downstream.thrift_spec), None, ), # 4
+  #)
+
+  def __init__(self, serverRole=None, sampled=None, baggage=None, downstream=None,):
+    self.serverRole = serverRole
+    self.sampled = sampled
+    self.baggage = baggage
+    self.downstream = downstream
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 1:
+        if ftype == TType.STRING:
+          self.serverRole = iprot.readString()
+        else:
+          iprot.skip(ftype)
+      elif fid == 2:
+        if ftype == TType.BOOL:
+          self.sampled = iprot.readBool()
+        else:
+          iprot.skip(ftype)
+      elif fid == 3:
+        if ftype == TType.STRING:
+          self.baggage = iprot.readString()
+        else:
+          iprot.skip(ftype)
+      elif fid == 4:
+        if ftype == TType.STRUCT:
+          self.downstream = Downstream()
+          self.downstream.read(iprot)
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('StartTraceRequest')
+    if self.serverRole is not None:
+      oprot.writeFieldBegin('serverRole', TType.STRING, 1)
+      oprot.writeString(self.serverRole)
+      oprot.writeFieldEnd()
+    if self.sampled is not None:
+      oprot.writeFieldBegin('sampled', TType.BOOL, 2)
+      oprot.writeBool(self.sampled)
+      oprot.writeFieldEnd()
+    if self.baggage is not None:
+      oprot.writeFieldBegin('baggage', TType.STRING, 3)
+      oprot.writeString(self.baggage)
+      oprot.writeFieldEnd()
+    if self.downstream is not None:
+      oprot.writeFieldBegin('downstream', TType.STRUCT, 4)
+      self.downstream.write(oprot)
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    if self.serverRole is None:
+      raise TProtocol.TProtocolException(message='Required field serverRole is unset!')
+    if self.sampled is None:
+      raise TProtocol.TProtocolException(message='Required field sampled is unset!')
+    if self.baggage is None:
+      raise TProtocol.TProtocolException(message='Required field baggage is unset!')
+    if self.downstream is None:
+      raise TProtocol.TProtocolException(message='Required field downstream is unset!')
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.serverRole)
+    value = (value * 31) ^ hash(self.sampled)
+    value = (value * 31) ^ hash(self.baggage)
+    value = (value * 31) ^ hash(self.downstream)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in self.__dict__.iteritems()]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)
+
+class JoinTraceRequest(object):
+  """
+  Attributes:
+   - serverRole
+   - downstream
+  """
+
+  #thrift_spec = (
+    #None, # 0
+    #(1, TType.STRING, 'serverRole', None, None, ), # 1
+    #(2, TType.STRUCT, 'downstream', (Downstream, Downstream.thrift_spec), None, ), # 2
+  #)
+
+  def __init__(self, serverRole=None, downstream=None,):
+    self.serverRole = serverRole
+    self.downstream = downstream
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 1:
+        if ftype == TType.STRING:
+          self.serverRole = iprot.readString()
+        else:
+          iprot.skip(ftype)
+      elif fid == 2:
+        if ftype == TType.STRUCT:
+          self.downstream = Downstream()
+          self.downstream.read(iprot)
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('JoinTraceRequest')
+    if self.serverRole is not None:
+      oprot.writeFieldBegin('serverRole', TType.STRING, 1)
+      oprot.writeString(self.serverRole)
+      oprot.writeFieldEnd()
+    if self.downstream is not None:
+      oprot.writeFieldBegin('downstream', TType.STRUCT, 2)
+      self.downstream.write(oprot)
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    if self.serverRole is None:
+      raise TProtocol.TProtocolException(message='Required field serverRole is unset!')
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.serverRole)
+    value = (value * 31) ^ hash(self.downstream)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in self.__dict__.iteritems()]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)
+
+class ObservedSpan(object):
+  """
+  Attributes:
+   - traceId
+   - sampled
+   - baggage
+  """
+
+  #thrift_spec = (
+    #None, # 0
+    #(1, TType.STRING, 'traceId', None, None, ), # 1
+    #(2, TType.BOOL, 'sampled', None, None, ), # 2
+    #(3, TType.STRING, 'baggage', None, None, ), # 3
+  #)
+
+  def __init__(self, traceId=None, sampled=None, baggage=None,):
+    self.traceId = traceId
+    self.sampled = sampled
+    self.baggage = baggage
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 1:
+        if ftype == TType.STRING:
+          self.traceId = iprot.readString()
+        else:
+          iprot.skip(ftype)
+      elif fid == 2:
+        if ftype == TType.BOOL:
+          self.sampled = iprot.readBool()
+        else:
+          iprot.skip(ftype)
+      elif fid == 3:
+        if ftype == TType.STRING:
+          self.baggage = iprot.readString()
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('ObservedSpan')
+    if self.traceId is not None:
+      oprot.writeFieldBegin('traceId', TType.STRING, 1)
+      oprot.writeString(self.traceId)
+      oprot.writeFieldEnd()
+    if self.sampled is not None:
+      oprot.writeFieldBegin('sampled', TType.BOOL, 2)
+      oprot.writeBool(self.sampled)
+      oprot.writeFieldEnd()
+    if self.baggage is not None:
+      oprot.writeFieldBegin('baggage', TType.STRING, 3)
+      oprot.writeString(self.baggage)
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    if self.traceId is None:
+      raise TProtocol.TProtocolException(message='Required field traceId is unset!')
+    if self.sampled is None:
+      raise TProtocol.TProtocolException(message='Required field sampled is unset!')
+    if self.baggage is None:
+      raise TProtocol.TProtocolException(message='Required field baggage is unset!')
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.traceId)
+    value = (value * 31) ^ hash(self.sampled)
+    value = (value * 31) ^ hash(self.baggage)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in self.__dict__.iteritems()]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)
+
+class TraceResponse(object):
+  """
+  Each server must include the information about the span it observed.
+  It can only be omitted from the response if notImplementedError field is not empty.
+  If the server was instructed to make a downstream call, it must embed the
+  downstream response in its own response.
+
+  Attributes:
+   - span
+   - downstream
+   - notImplementedError
+  """
+
+  #thrift_spec = (
+    #None, # 0
+    #(1, TType.STRUCT, 'span', (ObservedSpan, ObservedSpan.thrift_spec), None, ), # 1
+    #(2, TType.STRUCT, 'downstream', (TraceResponse, TraceResponse.thrift_spec), None, ), # 2
+    #(3, TType.STRING, 'notImplementedError', None, None, ), # 3
+  #)
+
+  def __init__(self, span=None, downstream=None, notImplementedError=None,):
+    self.span = span
+    self.downstream = downstream
+    self.notImplementedError = notImplementedError
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 1:
+        if ftype == TType.STRUCT:
+          self.span = ObservedSpan()
+          self.span.read(iprot)
+        else:
+          iprot.skip(ftype)
+      elif fid == 2:
+        if ftype == TType.STRUCT:
+          self.downstream = TraceResponse()
+          self.downstream.read(iprot)
+        else:
+          iprot.skip(ftype)
+      elif fid == 3:
+        if ftype == TType.STRING:
+          self.notImplementedError = iprot.readString()
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('TraceResponse')
+    if self.span is not None:
+      oprot.writeFieldBegin('span', TType.STRUCT, 1)
+      self.span.write(oprot)
+      oprot.writeFieldEnd()
+    if self.downstream is not None:
+      oprot.writeFieldBegin('downstream', TType.STRUCT, 2)
+      self.downstream.write(oprot)
+      oprot.writeFieldEnd()
+    if self.notImplementedError is not None:
+      oprot.writeFieldBegin('notImplementedError', TType.STRING, 3)
+      oprot.writeString(self.notImplementedError)
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    if self.notImplementedError is None:
+      raise TProtocol.TProtocolException(message='Required field notImplementedError is unset!')
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.span)
+    value = (value * 31) ^ hash(self.downstream)
+    value = (value * 31) ^ hash(self.notImplementedError)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in self.__dict__.iteritems()]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)

+ 55 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/TUDPTransport.py

@@ -0,0 +1,55 @@
+# Copyright (c) 2016 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+import logging
+
+from thrift.transport.TTransport import TTransportBase
+import socket
+
+logger = logging.getLogger('jaeger_tracing')
+
+
+class TUDPTransport(TTransportBase, object):
+    """
+    TUDPTransport implements just enough of the tornado transport interface
+    to work for blindly sending UDP packets.
+    """
+    def __init__(self, host, port, blocking=False):
+        self.transport_host = host
+        self.transport_port = port
+        self.transport_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+        if blocking:
+            blocking = 1
+        else:
+            blocking = 0
+        self.transport_sock.setblocking(blocking)
+
+    def write(self, buf):
+        """Raw write to the UDP socket."""
+        return self.transport_sock.sendto(
+            buf,
+            (self.transport_host, self.transport_port)
+        )
+
+    def isOpen(self):
+        """
+        isOpen for UDP is always true (there is no connection) as long
+        as we have a sock
+        """
+        return self.transport_sock is not None
+
+    def close(self):
+        self.transport_sock.close()
+        self.transport_sock = None

+ 33 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/__init__.py

@@ -0,0 +1,33 @@
+# Copyright (c) 2016 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+
+import sys
+
+# This is because thrift for python doesn't have 'package_prefix'.
+# The thrift compiled libraries refer to each other relative to their subdir.
+import jaeger_client.thrift_gen as modpath
+sys.path.append(modpath.__path__[0])
+
+__version__ = '4.0.0'
+
+from .tracer import Tracer  # noqa
+from .config import Config  # noqa
+from .span import Span  # noqa
+from .span_context import SpanContext  # noqa
+from .sampler import ConstSampler  # noqa
+from .sampler import ProbabilisticSampler  # noqa
+from .sampler import RateLimitingSampler  # noqa
+from .sampler import RemoteControlledSampler  # noqa

+ 321 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/codecs.py

@@ -0,0 +1,321 @@
+# Copyright (c) 2016 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+
+from opentracing import (
+    InvalidCarrierException,
+    SpanContextCorruptedException,
+)
+from .constants import (
+    BAGGAGE_HEADER_KEY,
+    BAGGAGE_HEADER_PREFIX,
+    DEBUG_ID_HEADER_KEY,
+    TRACE_ID_HEADER,
+)
+from .span_context import SpanContext
+from .constants import SAMPLED_FLAG, DEBUG_FLAG
+
+import six
+from six.moves import urllib_parse
+
+
+class Codec(object):
+    def inject(self, span_context, carrier):
+        raise NotImplementedError()
+
+    def extract(self, carrier):
+        raise NotImplementedError()
+
+
+class TextCodec(Codec):
+    def __init__(self,
+                 url_encoding=False,
+                 trace_id_header=TRACE_ID_HEADER,
+                 baggage_header_prefix=BAGGAGE_HEADER_PREFIX,
+                 debug_id_header=DEBUG_ID_HEADER_KEY,
+                 baggage_header=BAGGAGE_HEADER_KEY):
+        self.url_encoding = url_encoding
+        self.trace_id_header = trace_id_header.lower().replace('_', '-')
+        self.baggage_prefix = baggage_header_prefix.lower().replace('_', '-')
+        self.debug_id_header = debug_id_header.lower().replace('_', '-')
+        self.baggage_header = baggage_header
+        self.prefix_length = len(baggage_header_prefix)
+
+    def inject(self, span_context, carrier):
+        if not isinstance(carrier, dict):
+            raise InvalidCarrierException('carrier not a collection')
+        # Note: we do not url-encode the trace ID because the ':' separator
+        # is not a problem for HTTP header values
+        carrier[self.trace_id_header] = span_context_to_string(
+            trace_id=span_context.trace_id, span_id=span_context.span_id,
+            parent_id=span_context.parent_id, flags=span_context.flags)
+        baggage = span_context.baggage
+        if baggage:
+            for key, value in six.iteritems(baggage):
+                encoded_key = key
+                if self.url_encoding:
+                    if six.PY2 and isinstance(value, six.text_type):
+                        encoded_value = urllib_parse.quote(value.encode('utf-8'))
+                    else:
+                        encoded_value = urllib_parse.quote(value)
+                    # we assume that self.url_encoding means we are injecting
+                    # into HTTP headers. httplib does not like unicode strings
+                    # so we convert the key to utf-8. The URL-encoded value is
+                    # already a plain string.
+                    if six.PY2 and isinstance(key, six.text_type):
+                        encoded_key = key.encode('utf-8')
+                else:
+                    if six.PY3 and isinstance(value, six.binary_type):
+                        encoded_value = str(value, 'utf-8')
+                    else:
+                        encoded_value = value
+                if six.PY3 and isinstance(key, six.binary_type):
+                    encoded_key = str(key, 'utf-8')
+                # Leave the below print(), you will thank me next time you debug unicode strings
+                # print('adding baggage', key, '=>', value, 'as', encoded_key, '=>', encoded_value)
+                header_key = '%s%s' % (self.baggage_prefix, encoded_key)
+                carrier[header_key] = encoded_value
+
+    def extract(self, carrier):
+        if not hasattr(carrier, 'items'):
+            raise InvalidCarrierException('carrier not a collection')
+        trace_id, span_id, parent_id, flags = None, None, None, None
+        baggage = None
+        debug_id = None
+        for key, value in six.iteritems(carrier):
+            uc_key = key.lower()
+            if uc_key == self.trace_id_header:
+                if self.url_encoding:
+                    value = urllib_parse.unquote(value)
+                trace_id, span_id, parent_id, flags = \
+                    span_context_from_string(value)
+            elif uc_key.startswith(self.baggage_prefix):
+                if self.url_encoding:
+                    value = urllib_parse.unquote(value)
+                attr_key = key[self.prefix_length:]
+                if baggage is None:
+                    baggage = {attr_key.lower(): value}
+                else:
+                    baggage[attr_key.lower()] = value
+            elif uc_key == self.debug_id_header:
+                if self.url_encoding:
+                    value = urllib_parse.unquote(value)
+                debug_id = value
+            elif uc_key == self.baggage_header:
+                if self.url_encoding:
+                    value = urllib_parse.unquote(value)
+                baggage = self._parse_baggage_header(value, baggage)
+        if not trace_id or not span_id:
+            # reset all IDs
+            trace_id, span_id, parent_id, flags = None, None, None, None
+        if not trace_id and not debug_id and not baggage:
+            return None
+        return SpanContext(trace_id=trace_id, span_id=span_id,
+                           parent_id=parent_id, flags=flags,
+                           baggage=baggage, debug_id=debug_id)
+
+    def _parse_baggage_header(self, header, baggage):
+        for part in header.split(','):
+            kv = part.strip().split('=')
+            if len(kv) == 2:
+                if not baggage:
+                    baggage = {}
+                baggage[kv[0]] = kv[1]
+        return baggage
+
+
+class BinaryCodec(Codec):
+    """
+    BinaryCodec is a no-op.
+
+    """
+    def inject(self, span_context, carrier):
+        if not isinstance(carrier, bytearray):
+            raise InvalidCarrierException('carrier not a bytearray')
+        pass  # TODO binary encoding not implemented
+
+    def extract(self, carrier):
+        if not isinstance(carrier, bytearray):
+            raise InvalidCarrierException('carrier not a bytearray')
+        # TODO binary encoding not implemented
+        return None
+
+
+def span_context_to_string(trace_id, span_id, parent_id, flags):
+    """
+    Serialize span ID to a string
+        {trace_id}:{span_id}:{parent_id}:{flags}
+
+    Numbers are encoded as variable-length lower-case hex strings.
+    If parent_id is None, it is written as 0.
+
+    :param trace_id:
+    :param span_id:
+    :param parent_id:
+    :param flags:
+    """
+    parent_id = parent_id or 0
+    return '{:x}:{:x}:{:x}:{:x}'.format(trace_id, span_id, parent_id, flags)
+
+
+def span_context_from_string(value):
+    """
+    Decode span ID from a string into a TraceContext.
+    Returns None if the string value is malformed.
+
+    :param value: formatted {trace_id}:{span_id}:{parent_id}:{flags}
+    """
+    if type(value) is list and len(value) > 0:
+        # sometimes headers are presented as arrays of values
+        if len(value) > 1:
+            raise SpanContextCorruptedException(
+                'trace context must be a string or array of 1: "%s"' % value)
+        value = value[0]
+    if not isinstance(value, six.string_types):
+        raise SpanContextCorruptedException(
+            'trace context not a string "%s"' % value)
+    parts = value.split(':')
+    if len(parts) != 4:
+        raise SpanContextCorruptedException(
+            'malformed trace context "%s"' % value)
+    try:
+        trace_id = int(parts[0], 16)
+        span_id = int(parts[1], 16)
+        parent_id = int(parts[2], 16)
+        flags = int(parts[3], 16)
+        if trace_id < 1 or span_id < 1 or parent_id < 0 or flags < 0:
+            raise SpanContextCorruptedException(
+                'malformed trace context "%s"' % value)
+        if parent_id == 0:
+            parent_id = None
+        return trace_id, span_id, parent_id, flags
+    except ValueError as e:
+        raise SpanContextCorruptedException(
+            'malformed trace context "%s": %s' % (value, e))
+
+
+# String constants identifying the interop format.
+ZipkinSpanFormat = 'zipkin-span-format'
+
+
+class ZipkinCodec(Codec):
+    """
+    ZipkinCodec handles ZipkinSpanFormat, which is an interop format
+    used by TChannel.
+
+    """
+    def inject(self, span_context, carrier):
+        if not isinstance(carrier, dict):
+            raise InvalidCarrierException('carrier not a dictionary')
+        carrier['trace_id'] = span_context.trace_id
+        carrier['span_id'] = span_context.span_id
+        carrier['parent_id'] = span_context.parent_id
+        carrier['traceflags'] = span_context.flags
+
+    def extract(self, carrier):
+        if isinstance(carrier, dict):
+            trace_id = carrier.get('trace_id')
+            span_id = carrier.get('span_id')
+            parent_id = carrier.get('parent_id')
+            flags = carrier.get('traceflags')
+        else:
+            if hasattr(carrier, 'trace_id'):
+                trace_id = getattr(carrier, 'trace_id')
+            else:
+                raise InvalidCarrierException('carrier has no trace_id')
+            if hasattr(carrier, 'span_id'):
+                span_id = getattr(carrier, 'span_id')
+            else:
+                raise InvalidCarrierException('carrier has no span_id')
+            if hasattr(carrier, 'parent_id'):
+                parent_id = getattr(carrier, 'parent_id')
+            else:
+                raise InvalidCarrierException('carrier has no parent_id')
+            if hasattr(carrier, 'traceflags'):
+                flags = getattr(carrier, 'traceflags')
+            else:
+                raise InvalidCarrierException('carrier has no traceflags')
+        if not trace_id:
+            return None
+        return SpanContext(trace_id=trace_id, span_id=span_id,
+                           parent_id=parent_id, flags=flags,
+                           baggage=None)
+
+
+def header_to_hex(header):
+    if not isinstance(header, (str, six.text_type)):
+        raise SpanContextCorruptedException(
+            'malformed trace context "%s", expected hex string' % header)
+
+    try:
+        return int(header, 16)
+    except ValueError:
+        raise SpanContextCorruptedException(
+            'malformed trace context "%s", expected hex string' % header)
+
+
+class B3Codec(Codec):
+    """
+    Support B3 header properties
+    https://github.com/openzipkin/b3-propagation
+
+    """
+    trace_header = 'X-B3-TraceId'
+    _trace_header_lc = trace_header.lower()
+    span_header = 'X-B3-SpanId'
+    _span_header_lc = span_header.lower()
+    parent_span_header = 'X-B3-ParentSpanId'
+    _parent_span_header_lc = parent_span_header.lower()
+    sampled_header = 'X-B3-Sampled'
+    _sampled_header_lc = sampled_header.lower()
+    flags_header = 'X-B3-Flags'
+    _flags_header_lc = flags_header.lower()
+
+    def inject(self, span_context, carrier):
+        if not isinstance(carrier, dict):
+            raise InvalidCarrierException('carrier not a dictionary')
+        carrier[self.trace_header] = format(span_context.trace_id, 'x').zfill(16)
+        carrier[self.span_header] = format(span_context.span_id, 'x').zfill(16)
+        if span_context.parent_id is not None:
+            carrier[self.parent_span_header] = format(span_context.parent_id, 'x').zfill(16)
+        if span_context.flags & DEBUG_FLAG == DEBUG_FLAG:
+            carrier[self.flags_header] = '1'
+        elif span_context.flags & SAMPLED_FLAG == SAMPLED_FLAG:
+            carrier[self.sampled_header] = '1'
+
+    def extract(self, carrier):
+        if not isinstance(carrier, dict):
+            raise InvalidCarrierException('carrier not a dictionary')
+        trace_id = span_id = parent_id = None
+        flags = 0x00
+        for header_key, header_value in six.iteritems(carrier):
+            if header_value is None:
+                continue
+            lower_key = header_key.lower()
+            if lower_key == self._trace_header_lc:
+                trace_id = header_to_hex(header_value)
+            elif lower_key == self._span_header_lc:
+                span_id = header_to_hex(header_value)
+            elif lower_key == self._parent_span_header_lc:
+                parent_id = header_to_hex(header_value)
+            elif lower_key == self._sampled_header_lc and header_value == '1':
+                flags |= SAMPLED_FLAG
+            elif lower_key == self._flags_header_lc and header_value == '1':
+                flags |= DEBUG_FLAG
+        if not trace_id or not span_id:
+            return None
+        return SpanContext(trace_id=trace_id, span_id=span_id,
+                           parent_id=parent_id, flags=flags,
+                           baggage=None)

+ 442 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/config.py

@@ -0,0 +1,442 @@
+# Copyright (c) 2016-2018 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+
+import logging
+import os
+import threading
+
+import opentracing
+from opentracing.propagation import Format
+from . import Tracer
+from .local_agent_net import LocalAgentSender
+from .throttler import RemoteThrottler
+from .reporter import (
+    Reporter,
+    CompositeReporter,
+    LoggingReporter,
+)
+from .sampler import (
+    ConstSampler,
+    ProbabilisticSampler,
+    RateLimitingSampler,
+    RemoteControlledSampler,
+)
+from .constants import (
+    DEFAULT_SAMPLING_INTERVAL,
+    DEFAULT_FLUSH_INTERVAL,
+    SAMPLER_TYPE_CONST,
+    SAMPLER_TYPE_PROBABILISTIC,
+    SAMPLER_TYPE_RATE_LIMITING,
+    TRACE_ID_HEADER,
+    BAGGAGE_HEADER_PREFIX,
+    DEBUG_ID_HEADER_KEY,
+    MAX_TAG_VALUE_LENGTH,
+    DEFAULT_THROTTLER_REFRESH_INTERVAL,
+)
+from .metrics import LegacyMetricsFactory, MetricsFactory, Metrics
+from .utils import get_boolean, ErrorReporter
+from .codecs import B3Codec
+
+DEFAULT_REPORTING_HOST = 'localhost'
+DEFAULT_REPORTING_PORT = 6831
+DEFAULT_SAMPLING_PORT = 5778
+DEFAULT_THROTTLER_PORT = DEFAULT_SAMPLING_PORT
+LOCAL_AGENT_DEFAULT_ENABLED = True
+
+logger = logging.getLogger('jaeger_tracing')
+
+
+class Config(object):
+    """
+    Wraps a YAML configuration section for configuring Jaeger Tracer.
+
+    service_name is required, but can be passed either as constructor
+    parameter, or as config property.
+
+    Example:
+
+    .. code-block:: yaml
+
+        enabled: true
+        reporter_batch_size: 10
+        logging: true
+        metrics: true
+        sampler:
+            type: const
+            param: true
+
+    """
+
+    _initialized = False
+    _initialized_lock = threading.Lock()
+
+    def __init__(self, config, metrics=None, service_name=None, metrics_factory=None,
+                 validate=False, scope_manager=None):
+        """
+        :param metrics: an instance of Metrics class, or None. This parameter
+            has been deprecated, please use metrics_factory instead.
+        :param service_name: default service name.
+            Can be overwritten by config['service_name'].
+        :param metrics_factory: an instance of MetricsFactory class, or None.
+        :param scope_manager: an instance of a scope manager, or None for
+            default (ThreadLocalScopeManager).
+        """
+        if validate:
+            self._validate_config(config)
+        self.config = config
+        self.scope_manager = scope_manager
+        if get_boolean(self.config.get('metrics', True), True):
+            self._metrics_factory = metrics_factory or LegacyMetricsFactory(metrics or Metrics())
+        else:
+            # if metrics are explicitly disabled, use a dummy
+            self._metrics_factory = MetricsFactory()
+        self._service_name = config.get('service_name', service_name)
+        if not self._service_name:
+            raise ValueError('service_name required in the config or param')
+
+        self._error_reporter = ErrorReporter(
+            metrics=Metrics(),
+            logger=logger if self.logging else None,
+        )
+
+    def _validate_config(self, config):
+        allowed_keys = ['logging',
+                        'local_agent',
+                        'sampler',
+                        'tags',
+                        'enabled',
+                        'reporter_batch_size',
+                        'reporter_queue_size',
+                        'propagation',
+                        'max_tag_value_length',
+                        'reporter_flush_interval',
+                        'sampling_refresh_interval',
+                        'trace_id_header',
+                        'generate_128bit_trace_id',
+                        'baggage_header_prefix',
+                        'service_name',
+                        'throttler']
+        config_keys = config.keys()
+        unexpected_config_keys = [k for k in config_keys if k not in allowed_keys]
+        if unexpected_config_keys:
+            raise ValueError('Unexpected keys found in config:{}'.
+                             format(','.join(unexpected_config_keys)))
+
+    @property
+    def service_name(self):
+        return self._service_name
+
+    @property
+    def metrics(self):
+        return self._metrics
+
+    @property
+    def error_reporter(self):
+        return self._error_reporter
+
+    @property
+    def enabled(self):
+        return get_boolean(self.config.get('enabled', True), True)
+
+    @property
+    def reporter_batch_size(self):
+        return int(self.config.get('reporter_batch_size', 10))
+
+    @property
+    def reporter_queue_size(self):
+        return int(self.config.get('reporter_queue_size', 100))
+
+    @property
+    def logging(self):
+        return get_boolean(self.config.get('logging', False), False)
+
+    @property
+    def trace_id_header(self):
+        """
+        :return: Returns the name of the HTTP header used to encode trace ID
+        """
+        return self.config.get('trace_id_header', TRACE_ID_HEADER)
+
+    @property
+    def generate_128bit_trace_id(self):
+        """
+        :return: Returns boolean value to indicate if 128bit trace_id
+        generation is enabled
+        """
+        if 'generate_128bit_trace_id' in self.config:
+            return get_boolean(self.config['generate_128bit_trace_id'], False)
+        return os.getenv('JAEGER_TRACEID_128BIT') == 'true'
+
+    @property
+    def baggage_header_prefix(self):
+        """
+        :return: Returns the prefix for HTTP headers used to record baggage
+        items
+        """
+        return self.config.get('baggage_header_prefix', BAGGAGE_HEADER_PREFIX)
+
+    @property
+    def debug_id_header(self):
+        """
+        :return: Returns the name of HTTP header or a TextMap carrier key
+        which, if found in the carrier, forces the trace to be sampled as
+        "debug" trace. The value of the header is recorded as the tag on the
+        root span, so that the trace can be found in the UI using this value
+        as a correlation ID.
+        """
+        return self.config.get('debug_id_header', DEBUG_ID_HEADER_KEY)
+
+    @property
+    def max_tag_value_length(self):
+        """
+        :return: Returns max allowed tag value length. Longer values will
+        be truncated.
+        """
+        return self.config.get('max_tag_value_length', MAX_TAG_VALUE_LENGTH)
+
+    @property
+    def sampler(self):
+        sampler_config = self.config.get('sampler', {})
+        sampler_type = sampler_config.get('type', None)
+        sampler_param = sampler_config.get('param', None)
+        if not sampler_type:
+            return None
+        elif sampler_type == SAMPLER_TYPE_CONST:
+            return ConstSampler(decision=get_boolean(sampler_param, False))
+        elif sampler_type == SAMPLER_TYPE_PROBABILISTIC:
+            return ProbabilisticSampler(rate=float(sampler_param))
+        elif sampler_type in [SAMPLER_TYPE_RATE_LIMITING, 'rate_limiting']:
+            return RateLimitingSampler(
+                max_traces_per_second=float(sampler_param))
+
+        raise ValueError('Unknown sampler type %s' % sampler_type)
+
+    @property
+    def sampling_refresh_interval(self):
+        return self.config.get('sampling_refresh_interval',
+                               DEFAULT_SAMPLING_INTERVAL)
+
+    @property
+    def reporter_flush_interval(self):
+        return self.config.get('reporter_flush_interval',
+                               DEFAULT_FLUSH_INTERVAL)
+
+    def local_agent_group(self):
+        return self.config.get('local_agent', None)
+
+    @property
+    def local_agent_enabled(self):
+        # noinspection PyBroadException
+        try:
+            return get_boolean(self.local_agent_group().get('enabled',
+                               LOCAL_AGENT_DEFAULT_ENABLED),
+                               LOCAL_AGENT_DEFAULT_ENABLED)
+        except:  # noqa: E722
+            return LOCAL_AGENT_DEFAULT_ENABLED
+
+    @property
+    def local_agent_sampling_port(self):
+        # noinspection PyBroadException
+        try:
+            return int(self.local_agent_group()['sampling_port'])
+        except:  # noqa: E722
+            return DEFAULT_SAMPLING_PORT
+
+    @property
+    def local_agent_reporting_port(self):
+        # noinspection PyBroadException
+        try:
+            return int(self.local_agent_group()['reporting_port'])
+        except:  # noqa: E722
+            pass
+
+        try:
+            return int(os.getenv('JAEGER_AGENT_PORT'))
+        except:  # noqa: E722
+            return DEFAULT_REPORTING_PORT
+
+    @property
+    def local_agent_reporting_host(self):
+        # noinspection PyBroadException
+        try:
+            return self.local_agent_group()['reporting_host']
+        except:  # noqa: E722
+            pass
+
+        if os.getenv('JAEGER_AGENT_HOST') is not None:
+            return os.getenv('JAEGER_AGENT_HOST')
+        else:
+            return DEFAULT_REPORTING_HOST
+
+    @property
+    def max_operations(self):
+        return self.config.get('max_operations', None)
+
+    @property
+    def tags(self):
+        """
+        :return: Returns tags from config and `JAEGER_TAGS` environment variable
+        to use as process-wide tracer tags
+        """
+        tags = self.config.get('tags', {})
+        env_tags = os.environ.get('JAEGER_TAGS', '')
+        if env_tags:
+            for kv in env_tags.split(','):
+                key, value = kv.split('=')
+                tags[key.strip()] = value.strip()
+        return tags
+
+    @property
+    def propagation(self):
+        propagation = self.config.get('propagation')
+        if propagation == 'b3':
+            # replace the codec with a B3 enabled instance
+            return {Format.HTTP_HEADERS: B3Codec()}
+        return {}
+
+    def throttler_group(self):
+        return self.config.get('throttler', None)
+
+    @property
+    def throttler_port(self):
+        throttler_config = self.throttler_group()
+        if throttler_config is None:
+            return None
+        # noinspection PyBroadException
+        try:
+            return int(throttler_config['port'])
+        except:  # noqa: E722
+            return DEFAULT_THROTTLER_PORT
+
+    @property
+    def throttler_refresh_interval(self):
+        throttler_config = self.throttler_group()
+        if throttler_config is None:
+            return None
+        # noinspection PyBroadException
+        try:
+            return int(throttler_config['refresh_interval'])
+        except:  # noqa: E722
+            return DEFAULT_THROTTLER_REFRESH_INTERVAL
+
+    @staticmethod
+    def initialized():
+        with Config._initialized_lock:
+            return Config._initialized
+
+    def initialize_tracer(self, io_loop=None):
+        """
+        Initialize Jaeger Tracer based on the passed `jaeger_client.Config`.
+        Save it to `opentracing.tracer` global variable.
+        Only the first call to this method has any effect.
+        """
+
+        with Config._initialized_lock:
+            if Config._initialized:
+                logger.warn('Jaeger tracer already initialized, skipping')
+                return
+            Config._initialized = True
+
+        tracer = self.new_tracer(io_loop)
+
+        self._initialize_global_tracer(tracer=tracer)
+        return tracer
+
+    def new_tracer(self, io_loop=None):
+        """
+        Create a new Jaeger Tracer based on the passed `jaeger_client.Config`.
+        Does not set `opentracing.tracer` global variable.
+        """
+        channel = self._create_local_agent_channel(io_loop=io_loop)
+        sampler = self.sampler
+        if not sampler:
+            sampler = RemoteControlledSampler(
+                channel=channel,
+                service_name=self.service_name,
+                logger=logger,
+                metrics_factory=self._metrics_factory,
+                error_reporter=self.error_reporter,
+                sampling_refresh_interval=self.sampling_refresh_interval,
+                max_operations=self.max_operations)
+        logger.info('Using sampler %s', sampler)
+
+        reporter = Reporter(
+            channel=channel,
+            queue_capacity=self.reporter_queue_size,
+            batch_size=self.reporter_batch_size,
+            flush_interval=self.reporter_flush_interval,
+            logger=logger,
+            metrics_factory=self._metrics_factory,
+            error_reporter=self.error_reporter)
+
+        if self.logging:
+            reporter = CompositeReporter(reporter, LoggingReporter(logger))
+
+        if not self.throttler_group() is None:
+            throttler = RemoteThrottler(
+                channel,
+                self.service_name,
+                refresh_interval=self.throttler_refresh_interval,
+                logger=logger,
+                metrics_factory=self._metrics_factory,
+                error_reporter=self.error_reporter)
+        else:
+            throttler = None
+
+        return self.create_tracer(
+            reporter=reporter,
+            sampler=sampler,
+            throttler=throttler,
+        )
+
+    def create_tracer(self, reporter, sampler, throttler=None):
+        return Tracer(
+            service_name=self.service_name,
+            reporter=reporter,
+            sampler=sampler,
+            metrics_factory=self._metrics_factory,
+            trace_id_header=self.trace_id_header,
+            generate_128bit_trace_id=self.generate_128bit_trace_id,
+            baggage_header_prefix=self.baggage_header_prefix,
+            debug_id_header=self.debug_id_header,
+            tags=self.tags,
+            max_tag_value_length=self.max_tag_value_length,
+            extra_codecs=self.propagation,
+            throttler=throttler,
+            scope_manager=self.scope_manager,
+        )
+
+    def _initialize_global_tracer(self, tracer):
+        opentracing.tracer = tracer
+        logger.info('opentracing.tracer initialized to %s[app_name=%s]',
+                    tracer, self.service_name)
+
+    def _create_local_agent_channel(self, io_loop):
+        """
+        Create an out-of-process channel communicating to local jaeger-agent.
+        Spans are submitted as SOCK_DGRAM Thrift, sampling strategy is polled
+        via JSON HTTP.
+
+        :param self: instance of Config
+        """
+        logger.info('Initializing Jaeger Tracer with UDP reporter')
+        return LocalAgentSender(
+            host=self.local_agent_reporting_host,
+            sampling_port=self.local_agent_sampling_port,
+            reporting_port=self.local_agent_reporting_port,
+            throttling_port=self.throttler_port,
+            io_loop=io_loop
+        )

+ 97 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/constants.py

@@ -0,0 +1,97 @@
+# Copyright (c) 2016-2018 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import, unicode_literals, print_function
+
+from . import __version__
+
+import six
+
+# DEPRECATED: max number of bits to use when generating random ID
+MAX_ID_BITS = 64
+
+# Max number of bits allowed to use when generating Trace ID
+_max_trace_id_bits = 128
+
+# Max number of bits to use when generating random ID
+_max_id_bits = 64
+
+# How often remotely controlled sampler polls for sampling strategy
+DEFAULT_SAMPLING_INTERVAL = 60
+
+# How often remote reporter does a preemptive flush of its buffers
+DEFAULT_FLUSH_INTERVAL = 1
+
+# Name of the HTTP header used to encode trace ID
+TRACE_ID_HEADER = 'uber-trace-id' if six.PY3 else b'uber-trace-id'
+
+# Prefix for HTTP headers used to record baggage items
+BAGGAGE_HEADER_PREFIX = 'uberctx-' if six.PY3 else b'uberctx-'
+
+# The name of HTTP header or a TextMap carrier key which, if found in the
+# carrier, forces the trace to be sampled as "debug" trace. The value of the
+# header is recorded as the tag on the # root span, so that the trace can
+# be found in the UI using this value as a correlation ID.
+DEBUG_ID_HEADER_KEY = 'jaeger-debug-id'
+
+# The name of HTTP header or a TextMap carrier key that can be used to pass
+# additional baggage to the span, e.g. when executing an ad-hoc curl request:
+# curl -H 'jaeger-baggage: k1=v1,k2=v2' http://...
+BAGGAGE_HEADER_KEY = 'jaeger-baggage'
+
+
+JAEGER_CLIENT_VERSION = 'Python-%s' % __version__
+
+# Tracer-scoped tag that tells the version of Jaeger client library
+JAEGER_VERSION_TAG_KEY = 'jaeger.version'
+
+# Tracer-scoped tag that contains the hostname
+JAEGER_HOSTNAME_TAG_KEY = 'hostname'
+
+# Tracer-scoped tag that is used to report ip of the process.
+JAEGER_IP_TAG_KEY = 'ip'
+
+# the type of sampler that always makes the same decision.
+SAMPLER_TYPE_CONST = 'const'
+
+# the type of sampler that polls Jaeger agent for sampling strategy.
+SAMPLER_TYPE_REMOTE = 'remote'
+
+# the type of sampler that samples traces with a certain fixed probability.
+SAMPLER_TYPE_PROBABILISTIC = 'probabilistic'
+
+# the type of sampler that samples only up to a fixed number
+# of traces per second.
+# noinspection SpellCheckingInspection
+SAMPLER_TYPE_RATE_LIMITING = 'ratelimiting'
+
+# the type of sampler that samples only up to a fixed number
+# of traces per second.
+# noinspection SpellCheckingInspection
+SAMPLER_TYPE_LOWER_BOUND = 'lowerbound'
+
+# Tag key for unique client identifier. Used in throttler implementation.
+CLIENT_UUID_TAG_KEY = 'client-uuid'
+
+# max length for tag values. Longer values will be truncated.
+MAX_TAG_VALUE_LENGTH = 1024
+
+# Constant for sampled flag
+SAMPLED_FLAG = 0x01
+
+# Constant for debug flag
+DEBUG_FLAG = 0x02
+
+# How often throttler polls for credits
+DEFAULT_THROTTLER_REFRESH_INTERVAL = 5

+ 72 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/ioloop_util.py

@@ -0,0 +1,72 @@
+# Copyright (c) 2016 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+
+import sys
+from tornado import gen
+from tornado.concurrent import Future
+
+
+def submit(fn, io_loop, *args, **kwargs):
+    """Submit Tornado Coroutine to IOLoop.current().
+
+    :param fn: Tornado Coroutine to execute
+    :param io_loop: Tornado IOLoop where to schedule the coroutine
+    :param args: Args to pass to coroutine
+    :param kwargs: Kwargs to pass to coroutine
+    :returns tornado.concurrent.Future: future result of coroutine
+    """
+    future = Future()
+
+    def execute():
+        """Execute fn on the IOLoop."""
+        try:
+            result = gen.maybe_future(fn(*args, **kwargs))
+        except Exception:
+            # The function we ran didn't return a future and instead raised
+            # an exception. Let's pretend that it returned this dummy
+            # future with our stack trace.
+            f = gen.Future()
+            f.set_exc_info(sys.exc_info())
+            on_done(f)
+        else:
+            result.add_done_callback(on_done)
+
+    def on_done(tornado_future):
+        """
+        Set tornado.Future results to the concurrent.Future.
+        :param tornado_future:
+        """
+        exception = tornado_future.exception()
+        if not exception:
+            future.set_result(tornado_future.result())
+        else:
+            future.set_exception(exception)
+
+    io_loop.add_callback(execute)
+
+    return future
+
+
+def future_result(result):
+    future = Future()
+    future.set_result(result)
+    return future
+
+
+def future_exception(exception):
+    future = Future()
+    future.set_exception(exception)
+    return future

+ 105 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/local_agent_net.py

@@ -0,0 +1,105 @@
+# Copyright (c) 2016-2018 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+
+from threadloop import ThreadLoop
+import tornado
+import tornado.httpclient
+from tornado.concurrent import Future
+from tornado.httputil import url_concat
+from .TUDPTransport import TUDPTransport
+from thrift.transport.TTransport import TBufferedTransport
+
+
+class LocalAgentHTTP(object):
+
+    DEFAULT_TIMEOUT = 15
+
+    def __init__(self, host, port):
+        self.agent_http_host = host
+        self.agent_http_port = int(port)
+
+    def _request(self, path, timeout=DEFAULT_TIMEOUT, args=None):
+        http_client = tornado.httpclient.AsyncHTTPClient(
+            defaults=dict(request_timeout=timeout))
+        url = 'http://%s:%d/%s' % (self.agent_http_host, self.agent_http_port, path)
+        if args:
+            url = url_concat(url, args)
+        return http_client.fetch(url)
+
+    def request_sampling_strategy(self, service_name, timeout=DEFAULT_TIMEOUT):
+        return self._request('sampling', timeout=timeout, args={'service': service_name})
+
+    def request_throttling_credits(self,
+                                   service_name,
+                                   client_id,
+                                   operations,
+                                   timeout=DEFAULT_TIMEOUT):
+        return self._request('credits', timeout=timeout, args=[
+            ('service', service_name),
+            ('uuid', client_id),
+        ] + [('operations', op) for op in operations])
+
+
+class LocalAgentSender(TBufferedTransport):
+    """
+    LocalAgentSender implements everything necessary to communicate with
+    local jaeger-agent. This class is designed to work in tornado and
+    non-tornado environments. If in torndado, pass in the ioloop, if not
+    then LocalAgentSender will create one for itself.
+
+    NOTE: LocalAgentSender derives from TBufferedTransport. This will buffer
+    up all written data until flush() is called. Flush gets called at the
+    end of the batch span submission call.
+    """
+
+    def __init__(self, host, sampling_port, reporting_port, io_loop=None, throttling_port=None):
+        # IOLoop
+        self._thread_loop = None
+        self.io_loop = io_loop or self._create_new_thread_loop()
+
+        # HTTP sampling
+        self.local_agent_http = LocalAgentHTTP(host, sampling_port)
+
+        # HTTP throttling
+        if throttling_port:
+            self.throttling_http = LocalAgentHTTP(host, throttling_port)
+
+        # UDP reporting - this will only get written to after our flush() call.
+        # We are buffering things up because we are a TBufferedTransport.
+        udp = TUDPTransport(host, reporting_port)
+        TBufferedTransport.__init__(self, udp)
+
+    def _create_new_thread_loop(self):
+        """
+        Create a daemonized thread that will run Tornado IOLoop.
+        :return: the IOLoop backed by the new thread.
+        """
+        self._thread_loop = ThreadLoop()
+        if not self._thread_loop.is_ready():
+            self._thread_loop.start()
+        return self._thread_loop._io_loop
+
+    def readFrame(self):
+        """Empty read frame that is never ready"""
+        return Future()
+
+    # Pass-through for HTTP sampling strategies request.
+    def request_sampling_strategy(self, *args, **kwargs):
+        return self.local_agent_http.request_sampling_strategy(*args, **kwargs)
+
+    # Pass-through for HTTP throttling credit request.
+    def request_throttling_credits(self, *args, **kwargs):
+        return self.throttling_http.request_throttling_credits(*args, **kwargs)

+ 19 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/metrics/__init__.py

@@ -0,0 +1,19 @@
+# Copyright (c) 2018 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+
+from .metrics import MetricsFactory  # noqa
+from .metrics import LegacyMetricsFactory  # noqa
+from .metrics import Metrics  # noqa

+ 132 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/metrics/metrics.py

@@ -0,0 +1,132 @@
+# Copyright (c) 2016 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+from __future__ import division
+
+import six
+
+
+class MetricsFactory(object):
+    """Generates new metrics."""
+
+    def _noop(self, *args):
+        pass
+
+    def create_counter(self, name, tags=None):
+        """
+        Generates a new counter from the given name and tags and returns
+        a callable function used to increment the counter.
+        :param name: name of the counter
+        :param tags: tags for the counter
+        :return: a callable function which takes the value to increase
+        the counter by ie. def increment(value)
+        """
+        return self._noop
+
+    def create_timer(self, name, tags=None):
+        """
+        Generates a new timer from the given name and tags and returns
+        a callable function used to record a float duration in microseconds.
+        :param name: name of the timer
+        :param tags: tags for the timer
+        :return: a callable function which takes the duration to
+        record ie. def record(duration)
+        """
+        return self._noop
+
+    def create_gauge(self, name, tags=None):
+        """
+        Generates a new gauge from the given name and tags and returns
+        a callable function used to update the gauge.
+        :param name: name of the gauge
+        :param tags: tags for the gauge
+        :return: a callable function which takes the value to update
+        the gauge with ie. def update(value)
+        """
+        return self._noop
+
+
+class LegacyMetricsFactory(MetricsFactory):
+    """A MetricsFactory adapter for legacy Metrics class."""
+
+    def __init__(self, metrics):
+        self._metrics = metrics
+
+    def create_counter(self, name, tags=None):
+        key = self._get_key(name, tags)
+
+        def increment(value):
+            return self._metrics.count(key, value)
+        return increment
+
+    def create_timer(self, name, tags=None):
+        key = self._get_key(name, tags)
+
+        def record(value):
+            # Convert microseconds to milliseconds for legacy
+            return self._metrics.timing(key, value / 1000.0)
+        return record
+
+    def create_gauge(self, name, tags=None):
+        key = self._get_key(name, tags)
+
+        def update(value):
+            return self._metrics.gauge(key, value)
+        return update
+
+    def _get_key(self, name, tags=None):
+        if not tags:
+            return name
+        key = name
+        for k in sorted(six.iterkeys(tags)):
+            key = key + '.' + str(k) + '_' + str(tags[k])
+        return key
+
+
+class Metrics(object):
+    """
+    Provides an abstraction of metrics reporting framework.
+    This Class has been deprecated, please use MetricsFactory
+    instead.
+    """
+
+    def __init__(self, count=None, gauge=None, timing=None):
+        """
+        :param count: function (key, value) to emit counters
+        :param gauge: function (key, value) to emit gauges
+        :param timing: function (key, value in milliseconds) to
+            emit timings
+        """
+        self._count = count
+        self._gauge = gauge
+        self._timing = timing
+        if not callable(self._count):
+            self._count = None
+        if not callable(self._gauge):
+            self._gauge = None
+        if not callable(self._timing):
+            self._timing = None
+
+    def count(self, key, value):
+        if self._count:
+            self._count(key, value)
+
+    def timing(self, key, value):
+        if self._timing:
+            self._timing(key, value)
+
+    def gauge(self, key, value):
+        if self._gauge:
+            self._gauge(key, value)

+ 61 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/metrics/prometheus.py

@@ -0,0 +1,61 @@
+# Copyright (c) 2018, The Jaeger Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from jaeger_client.metrics import MetricsFactory
+from collections import defaultdict
+from prometheus_client import Counter, Gauge
+
+
+class PrometheusMetricsFactory(MetricsFactory):
+    """
+    Provides metrics backed by Prometheus
+    """
+    def __init__(self, namespace=''):
+        self._cache = defaultdict(object)
+        self._namespace = namespace
+
+    def _get_tag_name_list(self, tags):
+        if tags is None:
+            return []
+        tag_name_list = []
+        for key in tags.keys():
+            tag_name_list.append(key)
+        return tag_name_list
+
+    def _get_metric(self, metric, name, label_name_list):
+        cache_key = name + ''.join(label_name_list)
+        if self._cache.get(cache_key) is None:
+            self._cache[cache_key] = metric(name=name, documentation=name,
+                                            labelnames=label_name_list, namespace=self._namespace)
+        return self._cache[cache_key]
+
+    def create_counter(self, name, tags=None):
+        label_name_list = self._get_tag_name_list(tags)
+        counter = self._get_metric(Counter, name, label_name_list)
+        if tags is not None and len(tags) > 0:
+            counter = counter.labels(**tags)
+
+        def increment(value):
+            counter.inc(value)
+        return increment
+
+    def create_gauge(self, name, tags=None):
+        label_name_list = self._get_tag_name_list(tags)
+        gauge = self._get_metric(Gauge, name, label_name_list)
+        if tags is not None and len(tags) > 0:
+            gauge = gauge.labels(**tags)
+
+        def update(value):
+            gauge.set(value)
+        return update

+ 70 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/rate_limiter.py

@@ -0,0 +1,70 @@
+# Copyright (c) 2017 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import random
+import time
+
+
+class RateLimiter(object):
+    """
+    RateLimiter is based on leaky bucket algorithm, formulated in terms
+    of a credits balance that is replenished every time check_credit()
+    method is called (tick) by the amount proportional to the time
+    elapsed since the last tick, up to the max_balance. A call
+    to check_credit() takes a cost of an item we want to pay with the
+    balance. If the balance exceeds the cost of the item, the item is
+    "purchased" and the balance reduced, indicated by returned value of
+    true. Otherwise the balance is unchanged and return false.
+
+    This can be used to limit a rate of messages emitted by a service
+    by instantiating the Rate Limiter with the max number of messages a
+    service is allowed to emit per second, and calling check_credit(1.0)
+    for each message to determine if the message is within the rate limit.
+
+    It can also be used to limit the rate of traffic in bytes, by setting
+    credits_per_second to desired throughput as bytes/second, and calling
+    check_credit() with the actual message size.
+    """
+
+    def __init__(self, credits_per_second, max_balance):
+        self.credits_per_second = credits_per_second
+        self.max_balance = max_balance
+        self.balance = self.max_balance * random.random()
+        self.last_tick = self.timestamp()
+
+    @staticmethod
+    def timestamp():
+        return time.time()
+
+    def update(self, credits_per_second, max_balance):
+        self._update_balance()
+        self.credits_per_second = credits_per_second
+        # The new balance should be proportional to the old balance.
+        self.balance = max_balance * self.balance / self.max_balance
+        self.max_balance = max_balance
+
+    def check_credit(self, item_cost):
+        self._update_balance()
+        if self.balance >= item_cost:
+            self.balance -= item_cost
+            return True
+        return False
+
+    def _update_balance(self):
+        current_time = self.timestamp()
+        elapsed_time = current_time - self.last_tick
+        self.last_tick = current_time
+        self.balance += elapsed_time * self.credits_per_second
+        if self.balance > self.max_balance:
+            self.balance = self.max_balance

+ 277 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/reporter.py

@@ -0,0 +1,277 @@
+# Copyright (c) 2016 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+
+import logging
+import threading
+
+import tornado.gen
+import tornado.ioloop
+import tornado.queues
+import socket
+from tornado.concurrent import Future
+from .constants import DEFAULT_FLUSH_INTERVAL
+from . import thrift
+from . import ioloop_util
+from .metrics import Metrics, LegacyMetricsFactory
+from .utils import ErrorReporter
+
+from thrift.protocol import TCompactProtocol
+from jaeger_client.thrift_gen.agent import Agent
+
+default_logger = logging.getLogger('jaeger_tracing')
+
+
+class NullReporter(object):
+    """Ignores all spans."""
+    def report_span(self, span):
+        pass
+
+    def set_process(self, service_name, tags, max_length):
+        pass
+
+    def close(self):
+        fut = Future()
+        fut.set_result(True)
+        return fut
+
+
+class InMemoryReporter(NullReporter):
+    """Stores spans in memory and returns them via get_spans()."""
+    def __init__(self):
+        super(InMemoryReporter, self).__init__()
+        self.spans = []
+        self.lock = threading.Lock()
+
+    def report_span(self, span):
+        with self.lock:
+            self.spans.append(span)
+
+    def get_spans(self):
+        with self.lock:
+            return self.spans[:]
+
+
+class LoggingReporter(NullReporter):
+    """Logs all spans."""
+    def __init__(self, logger=None):
+        self.logger = logger if logger else default_logger
+
+    def report_span(self, span):
+        self.logger.info('Reporting span %s', span)
+
+
+class Reporter(NullReporter):
+    """Receives completed spans from Tracer and submits them out of process."""
+    def __init__(self, channel, queue_capacity=100, batch_size=10,
+                 flush_interval=DEFAULT_FLUSH_INTERVAL, io_loop=None,
+                 error_reporter=None, metrics=None, metrics_factory=None,
+                 **kwargs):
+        """
+        :param channel: a communication channel to jaeger-agent
+        :param queue_capacity: how many spans we can hold in memory before
+            starting to drop spans
+        :param batch_size: how many spans we can submit at once to Collector
+        :param flush_interval: how often the auto-flush is called (in seconds)
+        :param io_loop: which IOLoop to use. If None, try to get it from
+            channel (only works if channel is tchannel.sync)
+        :param error_reporter:
+        :param metrics: an instance of Metrics class, or None. This parameter
+            has been deprecated, please use metrics_factory instead.
+        :param metrics_factory: an instance of MetricsFactory class, or None.
+        :param kwargs:
+            'logger'
+        :return:
+        """
+        from threading import Lock
+
+        self._channel = channel
+        self.queue_capacity = queue_capacity
+        self.batch_size = batch_size
+        self.metrics_factory = metrics_factory or LegacyMetricsFactory(metrics or Metrics())
+        self.metrics = ReporterMetrics(self.metrics_factory)
+        self.error_reporter = error_reporter or ErrorReporter(Metrics())
+        self.logger = kwargs.get('logger', default_logger)
+        self.agent = Agent.Client(self._channel, self)
+
+        if queue_capacity < batch_size:
+            raise ValueError('Queue capacity cannot be less than batch size')
+
+        self.io_loop = io_loop or channel.io_loop
+        if self.io_loop is None:
+            self.logger.error('Jaeger Reporter has no IOLoop')
+        else:
+            self.queue = tornado.queues.Queue(maxsize=queue_capacity)
+            self.stop = object()
+            self.stopped = False
+            self.stop_lock = Lock()
+            self.flush_interval = flush_interval or None
+            self.io_loop.spawn_callback(self._consume_queue)
+
+        self._process_lock = Lock()
+        self._process = None
+
+    def set_process(self, service_name, tags, max_length):
+        with self._process_lock:
+            self._process = thrift.make_process(
+                service_name=service_name, tags=tags, max_length=max_length,
+            )
+
+    def report_span(self, span):
+        # We should not be calling `queue.put_nowait()` from random threads,
+        # only from the same IOLoop where the queue is consumed (T333431).
+        if tornado.ioloop.IOLoop.current(instance=False) == self.io_loop:
+            self._report_span_from_ioloop(span)
+        else:
+            self.io_loop.add_callback(self._report_span_from_ioloop, span)
+
+    def _report_span_from_ioloop(self, span):
+        try:
+            with self.stop_lock:
+                stopped = self.stopped
+            if stopped:
+                self.metrics.reporter_dropped(1)
+            else:
+                self.queue.put_nowait(span)
+        except tornado.queues.QueueFull:
+            self.metrics.reporter_dropped(1)
+
+    @tornado.gen.coroutine
+    def _consume_queue(self):
+        spans = []
+        stopped = False
+        while not stopped:
+            while len(spans) < self.batch_size:
+                try:
+                    # using timeout allows periodic flush with smaller packet
+                    timeout = self.flush_interval + self.io_loop.time() \
+                        if self.flush_interval and spans else None
+                    span = yield self.queue.get(timeout=timeout)
+                except tornado.gen.TimeoutError:
+                    break
+                else:
+                    if span == self.stop:
+                        stopped = True
+                        self.queue.task_done()
+                        # don't return yet, submit accumulated spans first
+                        break
+                    else:
+                        spans.append(span)
+            if spans:
+                yield self._submit(spans)
+                for _ in spans:
+                    self.queue.task_done()
+                spans = spans[:0]
+            self.metrics.reporter_queue_length(self.queue.qsize())
+        self.logger.info('Span publisher exited')
+
+    # method for protocol factory
+    def getProtocol(self, transport):
+        """
+        Implements Thrift ProtocolFactory interface
+        :param: transport:
+        :return: Thrift compact protocol
+        """
+        return TCompactProtocol.TCompactProtocol(transport)
+
+    @tornado.gen.coroutine
+    def _submit(self, spans):
+        if not spans:
+            return
+        with self._process_lock:
+            process = self._process
+            if not process:
+                return
+        try:
+            batch = thrift.make_jaeger_batch(spans=spans, process=process)
+            yield self._send(batch)
+            self.metrics.reporter_success(len(spans))
+        except socket.error as e:
+            self.metrics.reporter_failure(len(spans))
+            self.error_reporter.error(
+                'Failed to submit traces to jaeger-agent socket: %s', e)
+        except Exception as e:
+            self.metrics.reporter_failure(len(spans))
+            self.error_reporter.error(
+                'Failed to submit traces to jaeger-agent: %s', e)
+
+    @tornado.gen.coroutine
+    def _send(self, batch):
+        """
+        Send batch of spans out via thrift transport. Any exceptions thrown
+        will be caught above in the exception handler of _submit().
+        """
+        return self.agent.emitBatch(batch)
+
+    def close(self):
+        """
+        Ensure that all spans from the queue are submitted.
+        Returns Future that will be completed once the queue is empty.
+        """
+        with self.stop_lock:
+            self.stopped = True
+
+        return ioloop_util.submit(self._flush, io_loop=self.io_loop)
+
+    @tornado.gen.coroutine
+    def _flush(self):
+        yield self.queue.put(self.stop)
+        yield self.queue.join()
+
+
+class ReporterMetrics(object):
+    """Reporter specific metrics."""
+
+    def __init__(self, metrics_factory):
+        self.reporter_success = \
+            metrics_factory.create_counter(name='jaeger:reporter_spans', tags={'result': 'ok'})
+        self.reporter_failure = \
+            metrics_factory.create_counter(name='jaeger:reporter_spans', tags={'result': 'err'})
+        self.reporter_dropped = \
+            metrics_factory.create_counter(name='jaeger:reporter_spans', tags={'result': 'dropped'})
+        self.reporter_queue_length = \
+            metrics_factory.create_gauge(name='jaeger:reporter_queue_length')
+
+
+class CompositeReporter(NullReporter):
+    """Delegates reporting to one or more underlying reporters."""
+    def __init__(self, *reporters):
+        self.reporters = reporters
+
+    def set_process(self, service_name, tags, max_length):
+        for reporter in self.reporters:
+            reporter.set_process(service_name, tags, max_length)
+
+    def report_span(self, span):
+        for reporter in self.reporters:
+            reporter.report_span(span)
+
+    def close(self):
+        from threading import Lock
+        lock = Lock()
+        count = [0]
+        future = Future()
+
+        def on_close(_):
+            with lock:
+                count[0] += 1
+                if count[0] == len(self.reporters):
+                    future.set_result(True)
+
+        for reporter in self.reporters:
+            f = reporter.close()
+            f.add_done_callback(on_close)
+
+        return future

+ 538 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/sampler.py

@@ -0,0 +1,538 @@
+# Copyright (c) 2016-2018 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import, division
+
+import json
+import logging
+import random
+import six
+
+from threading import Lock
+from tornado.ioloop import PeriodicCallback
+from .constants import (
+    _max_id_bits,
+    DEFAULT_SAMPLING_INTERVAL,
+    SAMPLER_TYPE_CONST,
+    SAMPLER_TYPE_PROBABILISTIC,
+    SAMPLER_TYPE_RATE_LIMITING,
+    SAMPLER_TYPE_LOWER_BOUND,
+)
+from .metrics import Metrics, LegacyMetricsFactory
+from .utils import ErrorReporter
+from .rate_limiter import RateLimiter
+
+default_logger = logging.getLogger('jaeger_tracing')
+
+SAMPLER_TYPE_TAG_KEY = 'sampler.type'
+SAMPLER_PARAM_TAG_KEY = 'sampler.param'
+DEFAULT_SAMPLING_PROBABILITY = 0.001
+DEFAULT_LOWER_BOUND = 1.0 / (10.0 * 60.0)  # sample once every 10 minutes
+DEFAULT_MAX_OPERATIONS = 2000
+
+STRATEGIES_STR = 'perOperationStrategies'
+OPERATION_STR = 'operation'
+DEFAULT_LOWER_BOUND_STR = 'defaultLowerBoundTracesPerSecond'
+PROBABILISTIC_SAMPLING_STR = 'probabilisticSampling'
+SAMPLING_RATE_STR = 'samplingRate'
+DEFAULT_SAMPLING_PROBABILITY_STR = 'defaultSamplingProbability'
+OPERATION_SAMPLING_STR = 'operationSampling'
+MAX_TRACES_PER_SECOND_STR = 'maxTracesPerSecond'
+RATE_LIMITING_SAMPLING_STR = 'rateLimitingSampling'
+STRATEGY_TYPE_STR = 'strategyType'
+PROBABILISTIC_SAMPLING_STRATEGY = 'PROBABILISTIC'
+RATE_LIMITING_SAMPLING_STRATEGY = 'RATE_LIMITING'
+
+
+class Sampler(object):
+    """
+    Sampler is responsible for deciding if a particular span should be
+    "sampled", i.e. recorded in permanent storage.
+    """
+
+    def __init__(self, tags=None):
+        self._tags = tags
+
+    def is_sampled(self, trace_id, operation=''):
+        raise NotImplementedError()
+
+    def close(self):
+        raise NotImplementedError()
+
+    def __eq__(self, other):
+        return (
+            isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+        )
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+
+class ConstSampler(Sampler):
+    """ConstSampler always returns the same decision."""
+
+    def __init__(self, decision):
+        super(ConstSampler, self).__init__(
+            tags={
+                SAMPLER_TYPE_TAG_KEY: SAMPLER_TYPE_CONST,
+                SAMPLER_PARAM_TAG_KEY: decision,
+            }
+        )
+        self.decision = decision
+
+    def is_sampled(self, trace_id, operation=''):
+        return self.decision, self._tags
+
+    def close(self):
+        pass
+
+    def __str__(self):
+        return 'ConstSampler(%s)' % self.decision
+
+
+class ProbabilisticSampler(Sampler):
+    """
+    A sampler that randomly samples a certain percentage of traces specified
+    by the samplingRate, in the range between 0.0 and 1.0.
+
+    It relies on the fact that new trace IDs are 64bit random numbers
+    themselves, thus making the sampling decision without generating a new
+    random number, but simply calculating if traceID < (samplingRate * 2^64).
+    Note that we actually ignore (zero out) the most significant bit.
+    """
+
+    def __init__(self, rate):
+        super(ProbabilisticSampler, self).__init__(
+            tags={
+                SAMPLER_TYPE_TAG_KEY: SAMPLER_TYPE_PROBABILISTIC,
+                SAMPLER_PARAM_TAG_KEY: rate,
+            }
+        )
+        assert 0.0 <= rate <= 1.0, 'Sampling rate must be between 0.0 and 1.0'
+        self.rate = rate
+        self.max_number = 1 << _max_id_bits
+        self.boundary = rate * self.max_number
+
+    def is_sampled(self, trace_id, operation=''):
+        trace_id = trace_id & (self.max_number - 1)
+        return trace_id < self.boundary, self._tags
+
+    def close(self):
+        pass
+
+    def __str__(self):
+        return 'ProbabilisticSampler(%s)' % self.rate
+
+
+class RateLimitingSampler(Sampler):
+    """
+    Samples at most max_traces_per_second. The distribution of sampled
+    traces follows burstiness of the service, i.e. a service with uniformly
+    distributed requests will have those requests sampled uniformly as well,
+    but if requests are bursty, especially sub-second, then a number of
+    sequential requests can be sampled each second.
+    """
+
+    def __init__(self, max_traces_per_second=10):
+        super(RateLimitingSampler, self).__init__()
+        self.rate_limiter = None
+        self._init(max_traces_per_second)
+
+    def _init(self, max_traces_per_second):
+        assert max_traces_per_second >= 0, \
+            'max_traces_per_second must not be negative'
+        self._tags = {
+            SAMPLER_TYPE_TAG_KEY: SAMPLER_TYPE_RATE_LIMITING,
+            SAMPLER_PARAM_TAG_KEY: max_traces_per_second,
+        }
+        self.traces_per_second = max_traces_per_second
+        max_balance = max(self.traces_per_second, 1.0)
+        if not self.rate_limiter:
+            self.rate_limiter = RateLimiter(
+                credits_per_second=self.traces_per_second,
+                max_balance=max_balance
+            )
+        else:
+            self.rate_limiter.update(max_traces_per_second, max_balance)
+
+    def is_sampled(self, trace_id, operation=''):
+        return self.rate_limiter.check_credit(1.0), self._tags
+
+    def close(self):
+        pass
+
+    def __eq__(self, other):
+        """The last_tick and balance fields can be different"""
+        if not isinstance(other, self.__class__):
+            return False
+        d1 = dict(self.rate_limiter.__dict__)
+        d2 = dict(other.rate_limiter.__dict__)
+        d1['balance'] = d2['balance']
+        d1['last_tick'] = d2['last_tick']
+        return d1 == d2
+
+    def update(self, max_traces_per_second):
+        if self.traces_per_second == max_traces_per_second:
+            return False
+        self._init(max_traces_per_second)
+        return True
+
+    def __str__(self):
+        return 'RateLimitingSampler(%s)' % self.traces_per_second
+
+
+class GuaranteedThroughputProbabilisticSampler(Sampler):
+    """
+    A sampler that leverages both ProbabilisticSampler and RateLimitingSampler.
+    The RateLimitingSampler is used as a guaranteed lower bound sampler such
+    that every operation is sampled at least once in a time interval defined by
+    the lower_bound. ie a lower_bound of 1.0 / (60 * 10) will sample an
+    operation at least once every 10 minutes.
+
+    The ProbabilisticSampler is given higher priority when tags are emitted,
+    ie. if is_sampled() for both samplers return true, the tags for
+    ProbabilisticSampler will be used.
+    """
+    def __init__(self, operation, lower_bound, rate):
+        super(GuaranteedThroughputProbabilisticSampler, self).__init__(
+            tags={
+                SAMPLER_TYPE_TAG_KEY: SAMPLER_TYPE_LOWER_BOUND,
+                SAMPLER_PARAM_TAG_KEY: rate,
+            }
+        )
+        self.probabilistic_sampler = ProbabilisticSampler(rate)
+        self.lower_bound_sampler = RateLimitingSampler(lower_bound)
+        self.operation = operation
+        self.rate = rate
+        self.lower_bound = lower_bound
+
+    def is_sampled(self, trace_id, operation=''):
+        sampled, tags = \
+            self.probabilistic_sampler.is_sampled(trace_id, operation)
+        if sampled:
+            self.lower_bound_sampler.is_sampled(trace_id, operation)
+            return True, tags
+        sampled, _ = self.lower_bound_sampler.is_sampled(trace_id, operation)
+        return sampled, self._tags
+
+    def close(self):
+        self.probabilistic_sampler.close()
+        self.lower_bound_sampler.close()
+
+    def update(self, lower_bound, rate):
+        # (NB) This function should only be called while holding a Write lock.
+        if self.rate != rate:
+            self.probabilistic_sampler = ProbabilisticSampler(rate)
+            self.rate = rate
+            self._tags = {
+                SAMPLER_TYPE_TAG_KEY: SAMPLER_TYPE_LOWER_BOUND,
+                SAMPLER_PARAM_TAG_KEY: rate,
+            }
+        if self.lower_bound != lower_bound:
+            self.lower_bound_sampler.update(lower_bound)
+            self.lower_bound = lower_bound
+
+    def __str__(self):
+        return 'GuaranteedThroughputProbabilisticSampler(%s, %f, %f)' \
+               % (self.operation, self.rate, self.lower_bound)
+
+
+class AdaptiveSampler(Sampler):
+    """
+    A sampler that leverages both ProbabilisticSampler and RateLimitingSampler
+    via the GuaranteedThroughputProbabilisticSampler. This sampler keeps track
+    of all operations and delegates calls the the respective
+    GuaranteedThroughputProbabilisticSampler.
+    """
+    def __init__(self, strategies, max_operations):
+        super(AdaptiveSampler, self).__init__()
+
+        samplers = {}
+        for strategy in strategies.get(STRATEGIES_STR, []):
+            operation = strategy.get(OPERATION_STR)
+            sampler = GuaranteedThroughputProbabilisticSampler(
+                operation,
+                strategies.get(DEFAULT_LOWER_BOUND_STR, DEFAULT_LOWER_BOUND),
+                get_sampling_probability(strategy)
+            )
+            samplers[operation] = sampler
+
+        self.samplers = samplers
+        self.default_sampler = \
+            ProbabilisticSampler(strategies.get(DEFAULT_SAMPLING_PROBABILITY_STR,
+                                                DEFAULT_SAMPLING_PROBABILITY))
+        self.default_sampling_probability = \
+            strategies.get(DEFAULT_SAMPLING_PROBABILITY_STR, DEFAULT_SAMPLING_PROBABILITY)
+        self.lower_bound = strategies.get(DEFAULT_LOWER_BOUND_STR, DEFAULT_LOWER_BOUND)
+        self.max_operations = max_operations
+
+    def is_sampled(self, trace_id, operation=''):
+        sampler = self.samplers.get(operation)
+        if not sampler:
+            if len(self.samplers) >= self.max_operations:
+                return self.default_sampler.is_sampled(trace_id, operation)
+            sampler = GuaranteedThroughputProbabilisticSampler(
+                operation,
+                self.lower_bound,
+                self.default_sampling_probability
+            )
+            self.samplers[operation] = sampler
+            return sampler.is_sampled(trace_id, operation)
+        return sampler.is_sampled(trace_id, operation)
+
+    def update(self, strategies):
+        # (NB) This function should only be called while holding a Write lock.
+        for strategy in strategies.get(STRATEGIES_STR, []):
+            operation = strategy.get(OPERATION_STR)
+            lower_bound = strategies.get(DEFAULT_LOWER_BOUND_STR, DEFAULT_LOWER_BOUND)
+            sampling_rate = get_sampling_probability(strategy)
+            sampler = self.samplers.get(operation)
+            if not sampler:
+                sampler = GuaranteedThroughputProbabilisticSampler(
+                    operation,
+                    lower_bound,
+                    sampling_rate
+                )
+                self.samplers[operation] = sampler
+            else:
+                sampler.update(lower_bound, sampling_rate)
+        self.lower_bound = strategies.get(DEFAULT_LOWER_BOUND_STR, DEFAULT_LOWER_BOUND)
+        if self.default_sampling_probability != strategies.get(DEFAULT_SAMPLING_PROBABILITY_STR,
+                                                               DEFAULT_SAMPLING_PROBABILITY):
+            self.default_sampling_probability = \
+                strategies.get(DEFAULT_SAMPLING_PROBABILITY_STR, DEFAULT_SAMPLING_PROBABILITY)
+            self.default_sampler = \
+                ProbabilisticSampler(self.default_sampling_probability)
+
+    def close(self):
+        for _, sampler in six.iteritems(self.samplers):
+            sampler.close()
+
+    def __str__(self):
+        return 'AdaptiveSampler(%f, %f, %d)' \
+               % (self.default_sampling_probability, self.lower_bound,
+                  self.max_operations)
+
+
+class RemoteControlledSampler(Sampler):
+    """Periodically loads the sampling strategy from a remote server."""
+    def __init__(self, channel, service_name, **kwargs):
+        """
+        :param channel: channel for communicating with jaeger-agent
+        :param service_name: name of this application
+        :param kwargs: optional parameters
+            - init_sampler: initial value of the sampler,
+                else ProbabilisticSampler(0.001)
+            - sampling_refresh_interval: interval in seconds for polling
+              for new strategy
+            - logger: Logger instance
+            - metrics: metrics facade, used to emit metrics on errors.
+                This parameter has been deprecated, please use
+                metrics_factory instead.
+            - metrics_factory: used to generate metrics for errors
+            - error_reporter: ErrorReporter instance
+            - max_operations: maximum number of unique operations the
+              AdaptiveSampler will keep track of
+        :param init:
+        :return:
+        """
+        super(RemoteControlledSampler, self).__init__()
+        self._channel = channel
+        self.service_name = service_name
+        self.logger = kwargs.get('logger', default_logger)
+        self.sampler = kwargs.get('init_sampler')
+        self.sampling_refresh_interval = \
+            kwargs.get('sampling_refresh_interval') or DEFAULT_SAMPLING_INTERVAL
+        self.metrics_factory = kwargs.get('metrics_factory') \
+            or LegacyMetricsFactory(kwargs.get('metrics') or Metrics())
+        self.metrics = SamplerMetrics(self.metrics_factory)
+        self.error_reporter = kwargs.get('error_reporter') or \
+            ErrorReporter(Metrics())
+        self.max_operations = kwargs.get('max_operations') or \
+            DEFAULT_MAX_OPERATIONS
+
+        if not self.sampler:
+            self.sampler = ProbabilisticSampler(DEFAULT_SAMPLING_PROBABILITY)
+        else:
+            self.sampler.is_sampled(0)  # assert we got valid sampler API
+
+        self.lock = Lock()
+        self.running = True
+        self.periodic = None
+
+        self.io_loop = channel.io_loop
+        if not self.io_loop:
+            self.logger.error(
+                'Cannot acquire IOLoop, sampler will not be updated')
+        else:
+            # according to IOLoop docs, it's not safe to use timeout methods
+            # unless already running in the loop, so we use `add_callback`
+            self.io_loop.add_callback(self._init_polling)
+
+    def is_sampled(self, trace_id, operation=''):
+        with self.lock:
+            return self.sampler.is_sampled(trace_id, operation)
+
+    def _init_polling(self):
+        """
+        Bootstrap polling for sampling strategy.
+
+        To avoid spiky traffic from the samplers, we use a random delay
+        before the first poll.
+        """
+        with self.lock:
+            if not self.running:
+                return
+            r = random.Random()
+            delay = r.random() * self.sampling_refresh_interval
+            self.io_loop.call_later(delay=delay,
+                                    callback=self._delayed_polling)
+            self.logger.info(
+                'Delaying sampling strategy polling by %d sec', delay)
+
+    def _delayed_polling(self):
+        periodic = self._create_periodic_callback()
+        self._poll_sampling_manager()  # Initialize sampler now
+        with self.lock:
+            if not self.running:
+                return
+            self.periodic = periodic
+            periodic.start()  # start the periodic cycle
+            self.logger.info(
+                'Tracing sampler started with sampling refresh '
+                'interval %d sec', self.sampling_refresh_interval)
+
+    def _create_periodic_callback(self):
+        return PeriodicCallback(
+            callback=self._poll_sampling_manager,
+            # convert interval to milliseconds
+            callback_time=self.sampling_refresh_interval * 1000,
+            io_loop=self.io_loop)
+
+    def _sampling_request_callback(self, future):
+        exception = future.exception()
+        if exception:
+            self.metrics.sampler_query_failure(1)
+            self.error_reporter.error(
+                'Fail to get sampling strategy from jaeger-agent: %s',
+                exception)
+            return
+
+        response = future.result()
+
+        # In Python 3.5 response.body is of type bytes and json.loads() does only support str
+        # See: https://github.com/jaegertracing/jaeger-client-python/issues/180
+        if hasattr(response.body, 'decode') and callable(response.body.decode):
+            response_body = response.body.decode('utf-8')
+        else:
+            response_body = response.body
+
+        try:
+            sampling_strategies_response = json.loads(response_body)
+            self.metrics.sampler_retrieved(1)
+        except Exception as e:
+            self.metrics.sampler_query_failure(1)
+            self.error_reporter.error(
+                'Fail to parse sampling strategy '
+                'from jaeger-agent: %s [%s]', e, response_body)
+            return
+
+        self._update_sampler(sampling_strategies_response)
+        self.logger.debug('Tracing sampler set to %s', self.sampler)
+
+    def _update_sampler(self, response):
+        with self.lock:
+            try:
+                if response.get(OPERATION_SAMPLING_STR):
+                    self._update_adaptive_sampler(response.get(OPERATION_SAMPLING_STR))
+                else:
+                    self._update_rate_limiting_or_probabilistic_sampler(response)
+            except Exception as e:
+                self.metrics.sampler_update_failure(1)
+                self.error_reporter.error(
+                    'Fail to update sampler'
+                    'from jaeger-agent: %s [%s]', e, response)
+
+    def _update_adaptive_sampler(self, per_operation_strategies):
+        if isinstance(self.sampler, AdaptiveSampler):
+            self.sampler.update(per_operation_strategies)
+        else:
+            self.sampler = AdaptiveSampler(per_operation_strategies, self.max_operations)
+        self.metrics.sampler_updated(1)
+
+    def _update_rate_limiting_or_probabilistic_sampler(self, response):
+        s_type = response.get(STRATEGY_TYPE_STR)
+        new_sampler = self.sampler
+        if s_type == PROBABILISTIC_SAMPLING_STRATEGY:
+            sampling_rate = get_sampling_probability(response)
+            new_sampler = ProbabilisticSampler(rate=sampling_rate)
+        elif s_type == RATE_LIMITING_SAMPLING_STRATEGY:
+            mtps = get_rate_limit(response)
+            if mtps < 0 or mtps >= 500:
+                raise ValueError(
+                    'Rate limiting parameter not in [0, 500) range: %s' % mtps)
+            if isinstance(self.sampler, RateLimitingSampler):
+                if self.sampler.update(max_traces_per_second=mtps):
+                    self.metrics.sampler_updated(1)
+            else:
+                new_sampler = RateLimitingSampler(max_traces_per_second=mtps)
+        else:
+            raise ValueError('Unsupported sampling strategy type: %s' % s_type)
+
+        if self.sampler != new_sampler:
+            self.sampler = new_sampler
+            self.metrics.sampler_updated(1)
+
+    def _poll_sampling_manager(self):
+        self.logger.debug('Requesting tracing sampler refresh')
+        fut = self._channel.request_sampling_strategy(self.service_name)
+        fut.add_done_callback(self._sampling_request_callback)
+
+    def close(self):
+        with self.lock:
+            self.running = False
+            if self.periodic:
+                self.periodic.stop()
+
+
+def get_sampling_probability(strategy=None):
+    if not strategy:
+        return DEFAULT_SAMPLING_PROBABILITY
+    probability_strategy = strategy.get(PROBABILISTIC_SAMPLING_STR)
+    if not probability_strategy:
+        return DEFAULT_SAMPLING_PROBABILITY
+    return probability_strategy.get(SAMPLING_RATE_STR, DEFAULT_SAMPLING_PROBABILITY)
+
+
+def get_rate_limit(strategy=None):
+    if not strategy:
+        return DEFAULT_LOWER_BOUND
+    rate_limit_strategy = strategy.get(RATE_LIMITING_SAMPLING_STR)
+    if not rate_limit_strategy:
+        return DEFAULT_LOWER_BOUND
+    return rate_limit_strategy.get(MAX_TRACES_PER_SECOND_STR, DEFAULT_LOWER_BOUND)
+
+
+class SamplerMetrics(object):
+    """Sampler specific metrics."""
+
+    def __init__(self, metrics_factory):
+        self.sampler_retrieved = \
+            metrics_factory.create_counter(name='jaeger:sampler_queries', tags={'result': 'ok'})
+        self.sampler_query_failure = \
+            metrics_factory.create_counter(name='jaeger:sampler_queries', tags={'result': 'err'})
+        self.sampler_updated = \
+            metrics_factory.create_counter(name='jaeger:sampler_updates', tags={'result': 'ok'})
+        self.sampler_update_failure = \
+            metrics_factory.create_counter(name='jaeger:sampler_updates', tags={'result': 'err'})

+ 202 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/span.py

@@ -0,0 +1,202 @@
+# Copyright (c) 2016-2018 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+
+import six
+import threading
+import time
+
+import opentracing
+from opentracing.ext import tags as ext_tags
+from . import codecs, thrift
+from .constants import SAMPLED_FLAG, DEBUG_FLAG
+
+
+class Span(opentracing.Span):
+    """Implements opentracing.Span."""
+
+    __slots__ = ['_tracer', '_context',
+                 'operation_name', 'start_time', 'end_time',
+                 'logs', 'tags', 'update_lock']
+
+    def __init__(self, context, tracer, operation_name,
+                 tags=None, start_time=None, references=None):
+        super(Span, self).__init__(context=context, tracer=tracer)
+        self.operation_name = operation_name
+        self.start_time = start_time or time.time()
+        self.end_time = None
+        self.update_lock = threading.Lock()
+        self.references = references
+        # we store tags and logs as Thrift objects to avoid extra allocations
+        self.tags = []
+        self.logs = []
+        if tags:
+            for k, v in six.iteritems(tags):
+                self.set_tag(k, v)
+
+    def set_operation_name(self, operation_name):
+        """
+        Set or change the operation name.
+
+        :param operation_name: the new operation name
+        :return: Returns the Span itself, for call chaining.
+        """
+        with self.update_lock:
+            self.operation_name = operation_name
+        return self
+
+    def finish(self, finish_time=None):
+        """Indicate that the work represented by this span has been completed
+        or terminated, and is ready to be sent to the Reporter.
+
+        If any tags / logs need to be added to the span, it should be done
+        before calling finish(), otherwise they may be ignored.
+
+        :param finish_time: an explicit Span finish timestamp as a unix
+            timestamp per time.time()
+        """
+        if not self.is_sampled():
+            return
+
+        self.end_time = finish_time or time.time()  # no locking
+        self.tracer.report_span(self)
+
+    def set_tag(self, key, value):
+        """
+        :param key:
+        :param value:
+        """
+        with self.update_lock:
+            if key == ext_tags.SAMPLING_PRIORITY and not self._set_sampling_priority(value):
+                return self
+            if self.is_sampled():
+                tag = thrift.make_tag(
+                    key=key,
+                    value=value,
+                    max_length=self.tracer.max_tag_value_length, )
+                self.tags.append(tag)
+        return self
+
+    def _set_sampling_priority(self, value):
+        """
+        N.B. Caller must be holding update_lock.
+        """
+
+        # Ignore debug spans trying to re-enable debug.
+        if self.is_debug() and value:
+            return False
+
+        try:
+            value_num = int(value)
+        except ValueError:
+            return False
+        if value_num == 0:
+            self.context.flags &= ~(SAMPLED_FLAG | DEBUG_FLAG)
+            return False
+        if self.tracer.is_debug_allowed(self.operation_name):
+            self.context.flags |= SAMPLED_FLAG | DEBUG_FLAG
+            return True
+        return False
+
+    def log_kv(self, key_values, timestamp=None):
+        if self.is_sampled():
+            timestamp = timestamp if timestamp else time.time()
+            # TODO handle exception logging, 'python.exception.type' etc.
+            log = thrift.make_log(
+                timestamp=timestamp if timestamp else time.time(),
+                fields=key_values,
+                max_length=self._tracer.max_tag_value_length,
+            )
+            with self.update_lock:
+                self.logs.append(log)
+        return self
+
+    def set_baggage_item(self, key, value):
+        prev_value = self.get_baggage_item(key=key)
+        new_context = self.context.with_baggage_item(key=key, value=value)
+        with self.update_lock:
+            self._context = new_context
+        if self.is_sampled():
+            logs = {
+                'event': 'baggage',
+                'key': key,
+                'value': value,
+            }
+            if prev_value:
+                # TODO add metric for this
+                logs['override'] = 'true'
+            self.log_kv(key_values=logs)
+        return self
+
+    def get_baggage_item(self, key):
+        return self.context.baggage.get(key)
+
+    def is_sampled(self):
+        return self.context.flags & SAMPLED_FLAG == SAMPLED_FLAG
+
+    def is_debug(self):
+        return self.context.flags & DEBUG_FLAG == DEBUG_FLAG
+
+    def is_rpc(self):
+        for tag in self.tags:
+            if tag.key == ext_tags.SPAN_KIND:
+                return tag.vStr == ext_tags.SPAN_KIND_RPC_CLIENT or \
+                    tag.vStr == ext_tags.SPAN_KIND_RPC_SERVER
+        return False
+
+    def is_rpc_client(self):
+        for tag in self.tags:
+            if tag.key == ext_tags.SPAN_KIND:
+                return tag.vStr == ext_tags.SPAN_KIND_RPC_CLIENT
+        return False
+
+    @property
+    def trace_id(self):
+        return self.context.trace_id
+
+    @property
+    def span_id(self):
+        return self.context.span_id
+
+    @property
+    def parent_id(self):
+        return self.context.parent_id
+
+    @property
+    def flags(self):
+        return self.context.flags
+
+    def __repr__(self):
+        c = codecs.span_context_to_string(
+            trace_id=self.context.trace_id, span_id=self.context.span_id,
+            parent_id=self.context.parent_id, flags=self.context.flags)
+        return '%s %s.%s' % (c, self.tracer.service_name, self.operation_name)
+
+    def info(self, message, payload=None):
+        """DEPRECATED"""
+        if payload:
+            self.log(event=message, payload=payload)
+        else:
+            self.log(event=message)
+        return self
+
+    def error(self, message, payload=None):
+        """DEPRECATED"""
+        self.set_tag('error', True)
+        if payload:
+            self.log(event=message, payload=payload)
+        else:
+            self.log(event=message)
+        return self

+ 71 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/span_context.py

@@ -0,0 +1,71 @@
+# Copyright (c) 2016 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+
+import opentracing
+
+
+class SpanContext(opentracing.SpanContext):
+    __slots__ = ['trace_id', 'span_id', 'parent_id', 'flags',
+                 '_baggage', '_debug_id']
+
+    """Implements opentracing.SpanContext"""
+    def __init__(self, trace_id, span_id, parent_id, flags, baggage=None, debug_id=None):
+        self.trace_id = trace_id
+        self.span_id = span_id
+        self.parent_id = parent_id or None
+        self.flags = flags
+        self._baggage = baggage or opentracing.SpanContext.EMPTY_BAGGAGE
+        self._debug_id = debug_id
+
+    @property
+    def baggage(self):
+        return self._baggage or opentracing.SpanContext.EMPTY_BAGGAGE
+
+    def with_baggage_item(self, key, value):
+        baggage = dict(self._baggage)
+        if value is not None:
+            baggage[key] = value
+        else:
+            baggage.pop(key, None)
+        return SpanContext(
+            trace_id=self.trace_id,
+            span_id=self.span_id,
+            parent_id=self.parent_id,
+            flags=self.flags,
+            baggage=baggage,
+        )
+
+    @property
+    def has_trace(self):
+        return self.trace_id and self.span_id and self.flags is not None
+
+    @property
+    def is_debug_id_container_only(self):
+        """Deprecated, not used by Jaeger."""
+        return not self.trace_id and self._debug_id is not None
+
+    @property
+    def debug_id(self):
+        return self._debug_id
+
+    @staticmethod
+    def with_debug_id(debug_id):
+        """Deprecated, not used by Jaeger."""
+        ctx = SpanContext(
+            trace_id=None, span_id=None, parent_id=None, flags=None
+        )
+        ctx._debug_id = debug_id
+        return ctx

+ 239 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift.py

@@ -0,0 +1,239 @@
+# Copyright (c) 2016 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import six
+from opentracing.tracer import ReferenceType
+
+import jaeger_client.thrift_gen.jaeger.ttypes as ttypes
+import jaeger_client.thrift_gen.sampling.SamplingManager as sampling_manager
+
+_max_signed_port = (1 << 15) - 1
+_max_unsigned_port = (1 << 16)
+_max_signed_id = (1 << 63) - 1
+_max_unsigned_id = (1 << 64)
+
+if six.PY3:
+    long = int
+
+
+def _id_to_low(big_id):
+    """
+    :param big_id: id in integer
+    :return: Returns the right most 64 bits of big_id
+    """
+    if big_id is not None:
+        return big_id & (_max_unsigned_id - 1)
+
+
+def _id_to_high(big_id):
+    """
+    :param big_id: id in integer
+    :return: Returns the left most 64 bits of big_id
+    """
+    if big_id is not None:
+        return (big_id >> 64) & (_max_unsigned_id - 1)
+
+
+def id_to_int(big_id):
+    if big_id is None:
+        return None
+    # thrift defines ID fields as i64, which is signed,
+    # therefore we convert large IDs (> 2^63) to negative longs
+    if big_id > _max_signed_id:
+        big_id -= _max_unsigned_id
+    return big_id
+
+
+def _to_string(s):
+    try:
+        # Thrift in PY2 likes strings as bytes
+        if six.PY2 and isinstance(s, six.text_type):
+            return s.encode('utf-8')
+        else:
+            return str(s)
+    except Exception as e:
+        return str(e)
+
+
+def make_tag(key, value, max_length):
+    if type(value).__name__ == 'bool':  # isinstance doesnt work on booleans
+        return _make_bool_tag(
+            key=key,
+            value=value
+        )
+    elif isinstance(value, int):
+        return _make_long_tag(
+            key=key,
+            value=value
+        )
+    elif isinstance(value, float):
+        return _make_double_tag(
+            key=key,
+            value=value
+        )
+    else:
+        return _make_string_tag(
+            key=key,
+            value=value,
+            max_length=max_length
+        )
+
+
+def _make_string_tag(key, value, max_length):
+    key = _to_string(key)
+    value = _to_string(value)
+    if len(value) > max_length:
+        value = value[:max_length]
+    return ttypes.Tag(
+        key=key,
+        vStr=value,
+        vType=ttypes.TagType.STRING,
+    )
+
+
+def _make_long_tag(key, value):
+    key = _to_string(key)
+    return ttypes.Tag(
+        key=key,
+        vLong=value,
+        vType=ttypes.TagType.LONG
+    )
+
+
+def _make_double_tag(key, value):
+    key = _to_string(key)
+    return ttypes.Tag(
+        key=key,
+        vDouble=value,
+        vType=ttypes.TagType.DOUBLE
+    )
+
+
+def _make_bool_tag(key, value):
+    key = _to_string(key)
+    return ttypes.Tag(
+        key=key,
+        vBool=value,
+        vType=ttypes.TagType.BOOL
+    )
+
+
+def timestamp_micros(ts):
+    """
+    Convert a float Unix timestamp from time.time() into a long value
+    in microseconds, as required by Zipkin protocol.
+    :param ts:
+    :return:
+    """
+    return long(ts * 1000000)
+
+
+def make_tags(tags, max_length):
+    # TODO extend to support non-string tag values
+    return [
+        make_tag(key=k, value=v, max_length=max_length)
+        for k, v in six.iteritems(tags or {})
+    ]
+
+
+def make_log(timestamp, fields, max_length):
+    return ttypes.Log(
+        timestamp=timestamp_micros(ts=timestamp),
+        fields=make_tags(tags=fields, max_length=max_length),
+    )
+
+
+def make_process(service_name, tags, max_length):
+    return ttypes.Process(
+        serviceName=service_name,
+        tags=make_tags(tags=tags, max_length=max_length),
+    )
+
+
+def make_ref_type(span_ref_type):
+    if span_ref_type == ReferenceType.FOLLOWS_FROM:
+        return ttypes.SpanRefType.FOLLOWS_FROM
+    return ttypes.SpanRefType.CHILD_OF
+
+
+def make_references(references):
+    if not references:
+        return None
+    list_of_span_refs = list()
+    for span_ref in references:
+        list_of_span_refs.append(ttypes.SpanRef(
+            refType=make_ref_type(span_ref.type),
+            traceIdLow=id_to_int(_id_to_low(span_ref.referenced_context.trace_id)),
+            traceIdHigh=id_to_int(_id_to_high(span_ref.referenced_context.trace_id)),
+            spanId=id_to_int(span_ref.referenced_context.span_id),
+        ))
+    return list_of_span_refs
+
+
+def make_jaeger_batch(spans, process):
+    batch = ttypes.Batch(
+        spans=[],
+        process=process,
+    )
+    for span in spans:
+        with span.update_lock:
+            jaeger_span = ttypes.Span(
+                traceIdLow=id_to_int(_id_to_low(span.trace_id)),
+                traceIdHigh=id_to_int(_id_to_high(span.trace_id)),
+                spanId=id_to_int(span.span_id),
+                parentSpanId=id_to_int(span.parent_id) or 0,
+                operationName=span.operation_name,
+                references=make_references(span.references),
+                flags=span.context.flags,
+                startTime=timestamp_micros(span.start_time),
+                duration=timestamp_micros(span.end_time - span.start_time),
+                tags=span.tags,  # TODO
+                logs=span.logs,  # TODO
+            )
+        batch.spans.append(jaeger_span)
+    return batch
+
+
+def parse_sampling_strategy(response):
+    """
+    Parse SamplingStrategyResponse and converts to a Sampler.
+
+    :param response:
+    :return: Returns Go-style (value, error) pair
+    """
+    s_type = response.strategyType
+    if s_type == sampling_manager.SamplingStrategyType.PROBABILISTIC:
+        if response.probabilisticSampling is None:
+            return None, 'probabilisticSampling field is None'
+        sampling_rate = response.probabilisticSampling.samplingRate
+        if 0 <= sampling_rate <= 1.0:
+            from jaeger_client.sampler import ProbabilisticSampler
+            return ProbabilisticSampler(rate=sampling_rate), None
+        return None, (
+            'Probabilistic sampling rate not in [0, 1] range: %s' %
+            sampling_rate
+        )
+    elif s_type == sampling_manager.SamplingStrategyType.RATE_LIMITING:
+        if response.rateLimitingSampling is None:
+            return None, 'rateLimitingSampling field is None'
+        mtps = response.rateLimitingSampling.maxTracesPerSecond
+        if 0 <= mtps < 500:
+            from jaeger_client.sampler import RateLimitingSampler
+            return RateLimitingSampler(max_traces_per_second=mtps), None
+        return None, (
+            'Rate limiting parameter not in [0, 500] range: %s' % mtps
+        )
+    return None, (
+        'Unsupported sampling strategy type: %s' % s_type
+    )

+ 0 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/__init__.py


+ 286 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/agent/Agent.py

@@ -0,0 +1,286 @@
+#
+# Autogenerated by Thrift Compiler (0.9.3)
+#
+# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+#
+#  options string: py:new_style,tornado
+#
+import six
+from six.moves import xrange
+
+from thrift.Thrift import TType, TMessageType, TException, TApplicationException
+import logging
+from .ttypes import *
+from thrift.Thrift import TProcessor
+from thrift.transport import TTransport
+from thrift.protocol import TBinaryProtocol, TProtocol
+try:
+  from thrift.protocol import fastbinary
+except:
+  fastbinary = None
+
+from tornado import gen
+from tornado import concurrent
+from thrift.transport import TTransport
+
+class Iface(object):
+  def emitZipkinBatch(self, spans):
+    """
+    Parameters:
+     - spans
+    """
+    pass
+
+  def emitBatch(self, batch):
+    """
+    Parameters:
+     - batch
+    """
+    pass
+
+
+class Client(Iface):
+  def __init__(self, transport, iprot_factory, oprot_factory=None):
+    self._transport = transport
+    self._iprot_factory = iprot_factory
+    self._oprot_factory = (oprot_factory if oprot_factory is not None
+                           else iprot_factory)
+    self._seqid = 0
+    self._reqs = {}
+    self._transport.io_loop.spawn_callback(self._start_receiving)
+
+  @gen.engine
+  def _start_receiving(self):
+    while True:
+      try:
+        frame = yield self._transport.readFrame()
+      except TTransport.TTransportException as e:
+        for future in self._reqs.itervalues():
+          future.set_exception(e)
+        self._reqs = {}
+        return
+      tr = TTransport.TMemoryBuffer(frame)
+      iprot = self._iprot_factory.getProtocol(tr)
+      (fname, mtype, rseqid) = iprot.readMessageBegin()
+      future = self._reqs.pop(rseqid, None)
+      if not future:
+        # future has already been discarded
+        continue
+      method = getattr(self, 'recv_' + fname)
+      try:
+        result = method(iprot, mtype, rseqid)
+      except Exception as e:
+        future.set_exception(e)
+      else:
+        future.set_result(result)
+
+  def emitZipkinBatch(self, spans):
+    """
+    Parameters:
+     - spans
+    """
+    self._seqid += 1
+    self.send_emitZipkinBatch(spans)
+
+  def send_emitZipkinBatch(self, spans):
+    oprot = self._oprot_factory.getProtocol(self._transport)
+    oprot.writeMessageBegin('emitZipkinBatch', TMessageType.ONEWAY, self._seqid)
+    args = emitZipkinBatch_args()
+    args.spans = spans
+    args.write(oprot)
+    oprot.writeMessageEnd()
+    oprot.trans.flush()
+  def emitBatch(self, batch):
+    """
+    Parameters:
+     - batch
+    """
+    self._seqid += 1
+    self.send_emitBatch(batch)
+
+  def send_emitBatch(self, batch):
+    oprot = self._oprot_factory.getProtocol(self._transport)
+    oprot.writeMessageBegin('emitBatch', TMessageType.ONEWAY, self._seqid)
+    args = emitBatch_args()
+    args.batch = batch
+    args.write(oprot)
+    oprot.writeMessageEnd()
+    oprot.trans.flush()
+
+class Processor(Iface, TProcessor):
+  def __init__(self, handler):
+    self._handler = handler
+    self._processMap = {}
+    self._processMap["emitZipkinBatch"] = Processor.process_emitZipkinBatch
+    self._processMap["emitBatch"] = Processor.process_emitBatch
+
+  def process(self, iprot, oprot):
+    (name, type, seqid) = iprot.readMessageBegin()
+    if name not in self._processMap:
+      iprot.skip(TType.STRUCT)
+      iprot.readMessageEnd()
+      x = TApplicationException(TApplicationException.UNKNOWN_METHOD, 'Unknown function %s' % (name))
+      oprot.writeMessageBegin(name, TMessageType.EXCEPTION, seqid)
+      x.write(oprot)
+      oprot.writeMessageEnd()
+      oprot.trans.flush()
+      return
+    else:
+      return self._processMap[name](self, seqid, iprot, oprot)
+
+  @gen.coroutine
+  def process_emitZipkinBatch(self, seqid, iprot, oprot):
+    args = emitZipkinBatch_args()
+    args.read(iprot)
+    iprot.readMessageEnd()
+    yield gen.maybe_future(self._handler.emitZipkinBatch(args.spans))
+
+  @gen.coroutine
+  def process_emitBatch(self, seqid, iprot, oprot):
+    args = emitBatch_args()
+    args.read(iprot)
+    iprot.readMessageEnd()
+    yield gen.maybe_future(self._handler.emitBatch(args.batch))
+
+
+# HELPER FUNCTIONS AND STRUCTURES
+
+class emitZipkinBatch_args(object):
+  """
+  Attributes:
+   - spans
+  """
+
+  thrift_spec = (
+    None, # 0
+    (1, TType.LIST, 'spans', (TType.STRUCT,(zipkincore.ttypes.Span, zipkincore.ttypes.Span.thrift_spec)), None, ), # 1
+  )
+
+  def __init__(self, spans=None,):
+    self.spans = spans
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 1:
+        if ftype == TType.LIST:
+          self.spans = []
+          (_etype3, _size0) = iprot.readListBegin()
+          for _i4 in xrange(_size0):
+            _elem5 = zipkincore.ttypes.Span()
+            _elem5.read(iprot)
+            self.spans.append(_elem5)
+          iprot.readListEnd()
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('emitZipkinBatch_args')
+    if self.spans is not None:
+      oprot.writeFieldBegin('spans', TType.LIST, 1)
+      oprot.writeListBegin(TType.STRUCT, len(self.spans))
+      for iter6 in self.spans:
+        iter6.write(oprot)
+      oprot.writeListEnd()
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.spans)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in six.iteritems(self.__dict__)]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)
+
+class emitBatch_args(object):
+  """
+  Attributes:
+   - batch
+  """
+
+  thrift_spec = (
+    None, # 0
+    (1, TType.STRUCT, 'batch', (jaeger.ttypes.Batch, jaeger.ttypes.Batch.thrift_spec), None, ), # 1
+  )
+
+  def __init__(self, batch=None,):
+    self.batch = batch
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 1:
+        if ftype == TType.STRUCT:
+          self.batch = jaeger.ttypes.Batch()
+          self.batch.read(iprot)
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('emitBatch_args')
+    if self.batch is not None:
+      oprot.writeFieldBegin('batch', TType.STRUCT, 1)
+      self.batch.write(oprot)
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.batch)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in six.iteritems(self.__dict__)]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)

+ 1 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/agent/__init__.py

@@ -0,0 +1 @@
+__all__ = ['ttypes', 'constants', 'Agent']

+ 13 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/agent/constants.py

@@ -0,0 +1,13 @@
+#
+# Autogenerated by Thrift Compiler (0.9.3)
+#
+# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+#
+#  options string: py:new_style,tornado
+#
+import six
+from six.moves import xrange
+
+from thrift.Thrift import TType, TMessageType, TException, TApplicationException
+from .ttypes import *
+

+ 23 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/agent/ttypes.py

@@ -0,0 +1,23 @@
+#
+# Autogenerated by Thrift Compiler (0.9.3)
+#
+# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+#
+#  options string: py:new_style,tornado
+#
+import six
+from six.moves import xrange
+
+from thrift.Thrift import TType, TMessageType, TException, TApplicationException
+import jaeger.ttypes
+import zipkincore.ttypes
+
+
+from thrift.transport import TTransport
+from thrift.protocol import TBinaryProtocol, TProtocol
+try:
+  from thrift.protocol import fastbinary
+except:
+  fastbinary = None
+
+

+ 283 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/jaeger/Collector.py

@@ -0,0 +1,283 @@
+#
+# Autogenerated by Thrift Compiler (0.9.3)
+#
+# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+#
+#  options string: py:new_style,tornado
+#
+import six
+from six.moves import xrange
+
+from thrift.Thrift import TType, TMessageType, TException, TApplicationException
+import logging
+from .ttypes import *
+from thrift.Thrift import TProcessor
+from thrift.transport import TTransport
+from thrift.protocol import TBinaryProtocol, TProtocol
+try:
+  from thrift.protocol import fastbinary
+except:
+  fastbinary = None
+
+from tornado import gen
+from tornado import concurrent
+from thrift.transport import TTransport
+
+class Iface(object):
+  def submitBatches(self, batches):
+    """
+    Parameters:
+     - batches
+    """
+    pass
+
+
+class Client(Iface):
+  def __init__(self, transport, iprot_factory, oprot_factory=None):
+    self._transport = transport
+    self._iprot_factory = iprot_factory
+    self._oprot_factory = (oprot_factory if oprot_factory is not None
+                           else iprot_factory)
+    self._seqid = 0
+    self._reqs = {}
+    self._transport.io_loop.spawn_callback(self._start_receiving)
+
+  @gen.engine
+  def _start_receiving(self):
+    while True:
+      try:
+        frame = yield self._transport.readFrame()
+      except TTransport.TTransportException as e:
+        for future in self._reqs.itervalues():
+          future.set_exception(e)
+        self._reqs = {}
+        return
+      tr = TTransport.TMemoryBuffer(frame)
+      iprot = self._iprot_factory.getProtocol(tr)
+      (fname, mtype, rseqid) = iprot.readMessageBegin()
+      future = self._reqs.pop(rseqid, None)
+      if not future:
+        # future has already been discarded
+        continue
+      method = getattr(self, 'recv_' + fname)
+      try:
+        result = method(iprot, mtype, rseqid)
+      except Exception as e:
+        future.set_exception(e)
+      else:
+        future.set_result(result)
+
+  def submitBatches(self, batches):
+    """
+    Parameters:
+     - batches
+    """
+    self._seqid += 1
+    future = self._reqs[self._seqid] = concurrent.Future()
+    self.send_submitBatches(batches)
+    return future
+
+  def send_submitBatches(self, batches):
+    oprot = self._oprot_factory.getProtocol(self._transport)
+    oprot.writeMessageBegin('submitBatches', TMessageType.CALL, self._seqid)
+    args = submitBatches_args()
+    args.batches = batches
+    args.write(oprot)
+    oprot.writeMessageEnd()
+    oprot.trans.flush()
+
+  def recv_submitBatches(self, iprot, mtype, rseqid):
+    if mtype == TMessageType.EXCEPTION:
+      x = TApplicationException()
+      x.read(iprot)
+      iprot.readMessageEnd()
+      raise x
+    result = submitBatches_result()
+    result.read(iprot)
+    iprot.readMessageEnd()
+    if result.success is not None:
+      return result.success
+    raise TApplicationException(TApplicationException.MISSING_RESULT, "submitBatches failed: unknown result")
+
+
+class Processor(Iface, TProcessor):
+  def __init__(self, handler):
+    self._handler = handler
+    self._processMap = {}
+    self._processMap["submitBatches"] = Processor.process_submitBatches
+
+  def process(self, iprot, oprot):
+    (name, type, seqid) = iprot.readMessageBegin()
+    if name not in self._processMap:
+      iprot.skip(TType.STRUCT)
+      iprot.readMessageEnd()
+      x = TApplicationException(TApplicationException.UNKNOWN_METHOD, 'Unknown function %s' % (name))
+      oprot.writeMessageBegin(name, TMessageType.EXCEPTION, seqid)
+      x.write(oprot)
+      oprot.writeMessageEnd()
+      oprot.trans.flush()
+      return
+    else:
+      return self._processMap[name](self, seqid, iprot, oprot)
+
+  @gen.coroutine
+  def process_submitBatches(self, seqid, iprot, oprot):
+    args = submitBatches_args()
+    args.read(iprot)
+    iprot.readMessageEnd()
+    result = submitBatches_result()
+    result.success = yield gen.maybe_future(self._handler.submitBatches(args.batches))
+    oprot.writeMessageBegin("submitBatches", TMessageType.REPLY, seqid)
+    result.write(oprot)
+    oprot.writeMessageEnd()
+    oprot.trans.flush()
+
+
+# HELPER FUNCTIONS AND STRUCTURES
+
+class submitBatches_args(object):
+  """
+  Attributes:
+   - batches
+  """
+
+  thrift_spec = (
+    None, # 0
+    (1, TType.LIST, 'batches', (TType.STRUCT,(Batch, Batch.thrift_spec)), None, ), # 1
+  )
+
+  def __init__(self, batches=None,):
+    self.batches = batches
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 1:
+        if ftype == TType.LIST:
+          self.batches = []
+          (_etype45, _size42) = iprot.readListBegin()
+          for _i46 in xrange(_size42):
+            _elem47 = Batch()
+            _elem47.read(iprot)
+            self.batches.append(_elem47)
+          iprot.readListEnd()
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('submitBatches_args')
+    if self.batches is not None:
+      oprot.writeFieldBegin('batches', TType.LIST, 1)
+      oprot.writeListBegin(TType.STRUCT, len(self.batches))
+      for iter48 in self.batches:
+        iter48.write(oprot)
+      oprot.writeListEnd()
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.batches)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in six.iteritems(self.__dict__)]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)
+
+class submitBatches_result(object):
+  """
+  Attributes:
+   - success
+  """
+
+  thrift_spec = (
+    (0, TType.LIST, 'success', (TType.STRUCT,(BatchSubmitResponse, BatchSubmitResponse.thrift_spec)), None, ), # 0
+  )
+
+  def __init__(self, success=None,):
+    self.success = success
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 0:
+        if ftype == TType.LIST:
+          self.success = []
+          (_etype52, _size49) = iprot.readListBegin()
+          for _i53 in xrange(_size49):
+            _elem54 = BatchSubmitResponse()
+            _elem54.read(iprot)
+            self.success.append(_elem54)
+          iprot.readListEnd()
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('submitBatches_result')
+    if self.success is not None:
+      oprot.writeFieldBegin('success', TType.LIST, 0)
+      oprot.writeListBegin(TType.STRUCT, len(self.success))
+      for iter55 in self.success:
+        iter55.write(oprot)
+      oprot.writeListEnd()
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.success)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in six.iteritems(self.__dict__)]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)

+ 1 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/jaeger/__init__.py

@@ -0,0 +1 @@
+__all__ = ['ttypes', 'constants', 'Collector']

+ 13 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/jaeger/constants.py

@@ -0,0 +1,13 @@
+#
+# Autogenerated by Thrift Compiler (0.9.3)
+#
+# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+#
+#  options string: py:new_style,tornado
+#
+import six
+from six.moves import xrange
+
+from thrift.Thrift import TType, TMessageType, TException, TApplicationException
+from .ttypes import *
+

+ 893 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/jaeger/ttypes.py

@@ -0,0 +1,893 @@
+#
+# Autogenerated by Thrift Compiler (0.9.3)
+#
+# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+#
+#  options string: py:new_style,tornado
+#
+import six
+from six.moves import xrange
+
+from thrift.Thrift import TType, TMessageType, TException, TApplicationException
+
+from thrift.transport import TTransport
+from thrift.protocol import TBinaryProtocol, TProtocol
+try:
+  from thrift.protocol import fastbinary
+except:
+  fastbinary = None
+
+
+class TagType(object):
+  STRING = 0
+  DOUBLE = 1
+  BOOL = 2
+  LONG = 3
+  BINARY = 4
+
+  _VALUES_TO_NAMES = {
+    0: "STRING",
+    1: "DOUBLE",
+    2: "BOOL",
+    3: "LONG",
+    4: "BINARY",
+  }
+
+  _NAMES_TO_VALUES = {
+    "STRING": 0,
+    "DOUBLE": 1,
+    "BOOL": 2,
+    "LONG": 3,
+    "BINARY": 4,
+  }
+
+class SpanRefType(object):
+  CHILD_OF = 0
+  FOLLOWS_FROM = 1
+
+  _VALUES_TO_NAMES = {
+    0: "CHILD_OF",
+    1: "FOLLOWS_FROM",
+  }
+
+  _NAMES_TO_VALUES = {
+    "CHILD_OF": 0,
+    "FOLLOWS_FROM": 1,
+  }
+
+
+class Tag(object):
+  """
+  Attributes:
+   - key
+   - vType
+   - vStr
+   - vDouble
+   - vBool
+   - vLong
+   - vBinary
+  """
+
+  thrift_spec = (
+    None, # 0
+    (1, TType.STRING, 'key', None, None, ), # 1
+    (2, TType.I32, 'vType', None, None, ), # 2
+    (3, TType.STRING, 'vStr', None, None, ), # 3
+    (4, TType.DOUBLE, 'vDouble', None, None, ), # 4
+    (5, TType.BOOL, 'vBool', None, None, ), # 5
+    (6, TType.I64, 'vLong', None, None, ), # 6
+    (7, TType.STRING, 'vBinary', None, None, ), # 7
+  )
+
+  def __init__(self, key=None, vType=None, vStr=None, vDouble=None, vBool=None, vLong=None, vBinary=None,):
+    self.key = key
+    self.vType = vType
+    self.vStr = vStr
+    self.vDouble = vDouble
+    self.vBool = vBool
+    self.vLong = vLong
+    self.vBinary = vBinary
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 1:
+        if ftype == TType.STRING:
+          self.key = iprot.readString()
+        else:
+          iprot.skip(ftype)
+      elif fid == 2:
+        if ftype == TType.I32:
+          self.vType = iprot.readI32()
+        else:
+          iprot.skip(ftype)
+      elif fid == 3:
+        if ftype == TType.STRING:
+          self.vStr = iprot.readString()
+        else:
+          iprot.skip(ftype)
+      elif fid == 4:
+        if ftype == TType.DOUBLE:
+          self.vDouble = iprot.readDouble()
+        else:
+          iprot.skip(ftype)
+      elif fid == 5:
+        if ftype == TType.BOOL:
+          self.vBool = iprot.readBool()
+        else:
+          iprot.skip(ftype)
+      elif fid == 6:
+        if ftype == TType.I64:
+          self.vLong = iprot.readI64()
+        else:
+          iprot.skip(ftype)
+      elif fid == 7:
+        if ftype == TType.STRING:
+          self.vBinary = iprot.readString()
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('Tag')
+    if self.key is not None:
+      oprot.writeFieldBegin('key', TType.STRING, 1)
+      oprot.writeString(self.key)
+      oprot.writeFieldEnd()
+    if self.vType is not None:
+      oprot.writeFieldBegin('vType', TType.I32, 2)
+      oprot.writeI32(self.vType)
+      oprot.writeFieldEnd()
+    if self.vStr is not None:
+      oprot.writeFieldBegin('vStr', TType.STRING, 3)
+      oprot.writeString(self.vStr)
+      oprot.writeFieldEnd()
+    if self.vDouble is not None:
+      oprot.writeFieldBegin('vDouble', TType.DOUBLE, 4)
+      oprot.writeDouble(self.vDouble)
+      oprot.writeFieldEnd()
+    if self.vBool is not None:
+      oprot.writeFieldBegin('vBool', TType.BOOL, 5)
+      oprot.writeBool(self.vBool)
+      oprot.writeFieldEnd()
+    if self.vLong is not None:
+      oprot.writeFieldBegin('vLong', TType.I64, 6)
+      oprot.writeI64(self.vLong)
+      oprot.writeFieldEnd()
+    if self.vBinary is not None:
+      oprot.writeFieldBegin('vBinary', TType.STRING, 7)
+      oprot.writeString(self.vBinary)
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    if self.key is None:
+      raise TProtocol.TProtocolException(message='Required field key is unset!')
+    if self.vType is None:
+      raise TProtocol.TProtocolException(message='Required field vType is unset!')
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.key)
+    value = (value * 31) ^ hash(self.vType)
+    value = (value * 31) ^ hash(self.vStr)
+    value = (value * 31) ^ hash(self.vDouble)
+    value = (value * 31) ^ hash(self.vBool)
+    value = (value * 31) ^ hash(self.vLong)
+    value = (value * 31) ^ hash(self.vBinary)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in six.iteritems(self.__dict__)]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)
+
+class Log(object):
+  """
+  Attributes:
+   - timestamp
+   - fields
+  """
+
+  thrift_spec = (
+    None, # 0
+    (1, TType.I64, 'timestamp', None, None, ), # 1
+    (2, TType.LIST, 'fields', (TType.STRUCT,(Tag, Tag.thrift_spec)), None, ), # 2
+  )
+
+  def __init__(self, timestamp=None, fields=None,):
+    self.timestamp = timestamp
+    self.fields = fields
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 1:
+        if ftype == TType.I64:
+          self.timestamp = iprot.readI64()
+        else:
+          iprot.skip(ftype)
+      elif fid == 2:
+        if ftype == TType.LIST:
+          self.fields = []
+          (_etype3, _size0) = iprot.readListBegin()
+          for _i4 in xrange(_size0):
+            _elem5 = Tag()
+            _elem5.read(iprot)
+            self.fields.append(_elem5)
+          iprot.readListEnd()
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('Log')
+    if self.timestamp is not None:
+      oprot.writeFieldBegin('timestamp', TType.I64, 1)
+      oprot.writeI64(self.timestamp)
+      oprot.writeFieldEnd()
+    if self.fields is not None:
+      oprot.writeFieldBegin('fields', TType.LIST, 2)
+      oprot.writeListBegin(TType.STRUCT, len(self.fields))
+      for iter6 in self.fields:
+        iter6.write(oprot)
+      oprot.writeListEnd()
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    if self.timestamp is None:
+      raise TProtocol.TProtocolException(message='Required field timestamp is unset!')
+    if self.fields is None:
+      raise TProtocol.TProtocolException(message='Required field fields is unset!')
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.timestamp)
+    value = (value * 31) ^ hash(self.fields)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in six.iteritems(self.__dict__)]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)
+
+class SpanRef(object):
+  """
+  Attributes:
+   - refType
+   - traceIdLow
+   - traceIdHigh
+   - spanId
+  """
+
+  thrift_spec = (
+    None, # 0
+    (1, TType.I32, 'refType', None, None, ), # 1
+    (2, TType.I64, 'traceIdLow', None, None, ), # 2
+    (3, TType.I64, 'traceIdHigh', None, None, ), # 3
+    (4, TType.I64, 'spanId', None, None, ), # 4
+  )
+
+  def __init__(self, refType=None, traceIdLow=None, traceIdHigh=None, spanId=None,):
+    self.refType = refType
+    self.traceIdLow = traceIdLow
+    self.traceIdHigh = traceIdHigh
+    self.spanId = spanId
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 1:
+        if ftype == TType.I32:
+          self.refType = iprot.readI32()
+        else:
+          iprot.skip(ftype)
+      elif fid == 2:
+        if ftype == TType.I64:
+          self.traceIdLow = iprot.readI64()
+        else:
+          iprot.skip(ftype)
+      elif fid == 3:
+        if ftype == TType.I64:
+          self.traceIdHigh = iprot.readI64()
+        else:
+          iprot.skip(ftype)
+      elif fid == 4:
+        if ftype == TType.I64:
+          self.spanId = iprot.readI64()
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('SpanRef')
+    if self.refType is not None:
+      oprot.writeFieldBegin('refType', TType.I32, 1)
+      oprot.writeI32(self.refType)
+      oprot.writeFieldEnd()
+    if self.traceIdLow is not None:
+      oprot.writeFieldBegin('traceIdLow', TType.I64, 2)
+      oprot.writeI64(self.traceIdLow)
+      oprot.writeFieldEnd()
+    if self.traceIdHigh is not None:
+      oprot.writeFieldBegin('traceIdHigh', TType.I64, 3)
+      oprot.writeI64(self.traceIdHigh)
+      oprot.writeFieldEnd()
+    if self.spanId is not None:
+      oprot.writeFieldBegin('spanId', TType.I64, 4)
+      oprot.writeI64(self.spanId)
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    if self.refType is None:
+      raise TProtocol.TProtocolException(message='Required field refType is unset!')
+    if self.traceIdLow is None:
+      raise TProtocol.TProtocolException(message='Required field traceIdLow is unset!')
+    if self.traceIdHigh is None:
+      raise TProtocol.TProtocolException(message='Required field traceIdHigh is unset!')
+    if self.spanId is None:
+      raise TProtocol.TProtocolException(message='Required field spanId is unset!')
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.refType)
+    value = (value * 31) ^ hash(self.traceIdLow)
+    value = (value * 31) ^ hash(self.traceIdHigh)
+    value = (value * 31) ^ hash(self.spanId)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in six.iteritems(self.__dict__)]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)
+
+class Span(object):
+  """
+  Attributes:
+   - traceIdLow
+   - traceIdHigh
+   - spanId
+   - parentSpanId
+   - operationName
+   - references
+   - flags
+   - startTime
+   - duration
+   - tags
+   - logs
+  """
+
+  thrift_spec = (
+    None, # 0
+    (1, TType.I64, 'traceIdLow', None, None, ), # 1
+    (2, TType.I64, 'traceIdHigh', None, None, ), # 2
+    (3, TType.I64, 'spanId', None, None, ), # 3
+    (4, TType.I64, 'parentSpanId', None, None, ), # 4
+    (5, TType.STRING, 'operationName', None, None, ), # 5
+    (6, TType.LIST, 'references', (TType.STRUCT,(SpanRef, SpanRef.thrift_spec)), None, ), # 6
+    (7, TType.I32, 'flags', None, None, ), # 7
+    (8, TType.I64, 'startTime', None, None, ), # 8
+    (9, TType.I64, 'duration', None, None, ), # 9
+    (10, TType.LIST, 'tags', (TType.STRUCT,(Tag, Tag.thrift_spec)), None, ), # 10
+    (11, TType.LIST, 'logs', (TType.STRUCT,(Log, Log.thrift_spec)), None, ), # 11
+  )
+
+  def __init__(self, traceIdLow=None, traceIdHigh=None, spanId=None, parentSpanId=None, operationName=None, references=None, flags=None, startTime=None, duration=None, tags=None, logs=None,):
+    self.traceIdLow = traceIdLow
+    self.traceIdHigh = traceIdHigh
+    self.spanId = spanId
+    self.parentSpanId = parentSpanId
+    self.operationName = operationName
+    self.references = references
+    self.flags = flags
+    self.startTime = startTime
+    self.duration = duration
+    self.tags = tags
+    self.logs = logs
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 1:
+        if ftype == TType.I64:
+          self.traceIdLow = iprot.readI64()
+        else:
+          iprot.skip(ftype)
+      elif fid == 2:
+        if ftype == TType.I64:
+          self.traceIdHigh = iprot.readI64()
+        else:
+          iprot.skip(ftype)
+      elif fid == 3:
+        if ftype == TType.I64:
+          self.spanId = iprot.readI64()
+        else:
+          iprot.skip(ftype)
+      elif fid == 4:
+        if ftype == TType.I64:
+          self.parentSpanId = iprot.readI64()
+        else:
+          iprot.skip(ftype)
+      elif fid == 5:
+        if ftype == TType.STRING:
+          self.operationName = iprot.readString()
+        else:
+          iprot.skip(ftype)
+      elif fid == 6:
+        if ftype == TType.LIST:
+          self.references = []
+          (_etype10, _size7) = iprot.readListBegin()
+          for _i11 in xrange(_size7):
+            _elem12 = SpanRef()
+            _elem12.read(iprot)
+            self.references.append(_elem12)
+          iprot.readListEnd()
+        else:
+          iprot.skip(ftype)
+      elif fid == 7:
+        if ftype == TType.I32:
+          self.flags = iprot.readI32()
+        else:
+          iprot.skip(ftype)
+      elif fid == 8:
+        if ftype == TType.I64:
+          self.startTime = iprot.readI64()
+        else:
+          iprot.skip(ftype)
+      elif fid == 9:
+        if ftype == TType.I64:
+          self.duration = iprot.readI64()
+        else:
+          iprot.skip(ftype)
+      elif fid == 10:
+        if ftype == TType.LIST:
+          self.tags = []
+          (_etype16, _size13) = iprot.readListBegin()
+          for _i17 in xrange(_size13):
+            _elem18 = Tag()
+            _elem18.read(iprot)
+            self.tags.append(_elem18)
+          iprot.readListEnd()
+        else:
+          iprot.skip(ftype)
+      elif fid == 11:
+        if ftype == TType.LIST:
+          self.logs = []
+          (_etype22, _size19) = iprot.readListBegin()
+          for _i23 in xrange(_size19):
+            _elem24 = Log()
+            _elem24.read(iprot)
+            self.logs.append(_elem24)
+          iprot.readListEnd()
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('Span')
+    if self.traceIdLow is not None:
+      oprot.writeFieldBegin('traceIdLow', TType.I64, 1)
+      oprot.writeI64(self.traceIdLow)
+      oprot.writeFieldEnd()
+    if self.traceIdHigh is not None:
+      oprot.writeFieldBegin('traceIdHigh', TType.I64, 2)
+      oprot.writeI64(self.traceIdHigh)
+      oprot.writeFieldEnd()
+    if self.spanId is not None:
+      oprot.writeFieldBegin('spanId', TType.I64, 3)
+      oprot.writeI64(self.spanId)
+      oprot.writeFieldEnd()
+    if self.parentSpanId is not None:
+      oprot.writeFieldBegin('parentSpanId', TType.I64, 4)
+      oprot.writeI64(self.parentSpanId)
+      oprot.writeFieldEnd()
+    if self.operationName is not None:
+      oprot.writeFieldBegin('operationName', TType.STRING, 5)
+      oprot.writeString(self.operationName)
+      oprot.writeFieldEnd()
+    if self.references is not None:
+      oprot.writeFieldBegin('references', TType.LIST, 6)
+      oprot.writeListBegin(TType.STRUCT, len(self.references))
+      for iter25 in self.references:
+        iter25.write(oprot)
+      oprot.writeListEnd()
+      oprot.writeFieldEnd()
+    if self.flags is not None:
+      oprot.writeFieldBegin('flags', TType.I32, 7)
+      oprot.writeI32(self.flags)
+      oprot.writeFieldEnd()
+    if self.startTime is not None:
+      oprot.writeFieldBegin('startTime', TType.I64, 8)
+      oprot.writeI64(self.startTime)
+      oprot.writeFieldEnd()
+    if self.duration is not None:
+      oprot.writeFieldBegin('duration', TType.I64, 9)
+      oprot.writeI64(self.duration)
+      oprot.writeFieldEnd()
+    if self.tags is not None:
+      oprot.writeFieldBegin('tags', TType.LIST, 10)
+      oprot.writeListBegin(TType.STRUCT, len(self.tags))
+      for iter26 in self.tags:
+        iter26.write(oprot)
+      oprot.writeListEnd()
+      oprot.writeFieldEnd()
+    if self.logs is not None:
+      oprot.writeFieldBegin('logs', TType.LIST, 11)
+      oprot.writeListBegin(TType.STRUCT, len(self.logs))
+      for iter27 in self.logs:
+        iter27.write(oprot)
+      oprot.writeListEnd()
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    if self.traceIdLow is None:
+      raise TProtocol.TProtocolException(message='Required field traceIdLow is unset!')
+    if self.traceIdHigh is None:
+      raise TProtocol.TProtocolException(message='Required field traceIdHigh is unset!')
+    if self.spanId is None:
+      raise TProtocol.TProtocolException(message='Required field spanId is unset!')
+    if self.parentSpanId is None:
+      raise TProtocol.TProtocolException(message='Required field parentSpanId is unset!')
+    if self.operationName is None:
+      raise TProtocol.TProtocolException(message='Required field operationName is unset!')
+    if self.flags is None:
+      raise TProtocol.TProtocolException(message='Required field flags is unset!')
+    if self.startTime is None:
+      raise TProtocol.TProtocolException(message='Required field startTime is unset!')
+    if self.duration is None:
+      raise TProtocol.TProtocolException(message='Required field duration is unset!')
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.traceIdLow)
+    value = (value * 31) ^ hash(self.traceIdHigh)
+    value = (value * 31) ^ hash(self.spanId)
+    value = (value * 31) ^ hash(self.parentSpanId)
+    value = (value * 31) ^ hash(self.operationName)
+    value = (value * 31) ^ hash(self.references)
+    value = (value * 31) ^ hash(self.flags)
+    value = (value * 31) ^ hash(self.startTime)
+    value = (value * 31) ^ hash(self.duration)
+    value = (value * 31) ^ hash(self.tags)
+    value = (value * 31) ^ hash(self.logs)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in six.iteritems(self.__dict__)]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)
+
+class Process(object):
+  """
+  Attributes:
+   - serviceName
+   - tags
+  """
+
+  thrift_spec = (
+    None, # 0
+    (1, TType.STRING, 'serviceName', None, None, ), # 1
+    (2, TType.LIST, 'tags', (TType.STRUCT,(Tag, Tag.thrift_spec)), None, ), # 2
+  )
+
+  def __init__(self, serviceName=None, tags=None,):
+    self.serviceName = serviceName
+    self.tags = tags
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 1:
+        if ftype == TType.STRING:
+          self.serviceName = iprot.readString()
+        else:
+          iprot.skip(ftype)
+      elif fid == 2:
+        if ftype == TType.LIST:
+          self.tags = []
+          (_etype31, _size28) = iprot.readListBegin()
+          for _i32 in xrange(_size28):
+            _elem33 = Tag()
+            _elem33.read(iprot)
+            self.tags.append(_elem33)
+          iprot.readListEnd()
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('Process')
+    if self.serviceName is not None:
+      oprot.writeFieldBegin('serviceName', TType.STRING, 1)
+      oprot.writeString(self.serviceName)
+      oprot.writeFieldEnd()
+    if self.tags is not None:
+      oprot.writeFieldBegin('tags', TType.LIST, 2)
+      oprot.writeListBegin(TType.STRUCT, len(self.tags))
+      for iter34 in self.tags:
+        iter34.write(oprot)
+      oprot.writeListEnd()
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    if self.serviceName is None:
+      raise TProtocol.TProtocolException(message='Required field serviceName is unset!')
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.serviceName)
+    value = (value * 31) ^ hash(self.tags)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in six.iteritems(self.__dict__)]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)
+
+class Batch(object):
+  """
+  Attributes:
+   - process
+   - spans
+  """
+
+  thrift_spec = (
+    None, # 0
+    (1, TType.STRUCT, 'process', (Process, Process.thrift_spec), None, ), # 1
+    (2, TType.LIST, 'spans', (TType.STRUCT,(Span, Span.thrift_spec)), None, ), # 2
+  )
+
+  def __init__(self, process=None, spans=None,):
+    self.process = process
+    self.spans = spans
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 1:
+        if ftype == TType.STRUCT:
+          self.process = Process()
+          self.process.read(iprot)
+        else:
+          iprot.skip(ftype)
+      elif fid == 2:
+        if ftype == TType.LIST:
+          self.spans = []
+          (_etype38, _size35) = iprot.readListBegin()
+          for _i39 in xrange(_size35):
+            _elem40 = Span()
+            _elem40.read(iprot)
+            self.spans.append(_elem40)
+          iprot.readListEnd()
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('Batch')
+    if self.process is not None:
+      oprot.writeFieldBegin('process', TType.STRUCT, 1)
+      self.process.write(oprot)
+      oprot.writeFieldEnd()
+    if self.spans is not None:
+      oprot.writeFieldBegin('spans', TType.LIST, 2)
+      oprot.writeListBegin(TType.STRUCT, len(self.spans))
+      for iter41 in self.spans:
+        iter41.write(oprot)
+      oprot.writeListEnd()
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    if self.process is None:
+      raise TProtocol.TProtocolException(message='Required field process is unset!')
+    if self.spans is None:
+      raise TProtocol.TProtocolException(message='Required field spans is unset!')
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.process)
+    value = (value * 31) ^ hash(self.spans)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in six.iteritems(self.__dict__)]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)
+
+class BatchSubmitResponse(object):
+  """
+  Attributes:
+   - ok
+  """
+
+  thrift_spec = (
+    None, # 0
+    (1, TType.BOOL, 'ok', None, None, ), # 1
+  )
+
+  def __init__(self, ok=None,):
+    self.ok = ok
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 1:
+        if ftype == TType.BOOL:
+          self.ok = iprot.readBool()
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('BatchSubmitResponse')
+    if self.ok is not None:
+      oprot.writeFieldBegin('ok', TType.BOOL, 1)
+      oprot.writeBool(self.ok)
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    if self.ok is None:
+      raise TProtocol.TProtocolException(message='Required field ok is unset!')
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.ok)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in six.iteritems(self.__dict__)]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)

+ 266 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/sampling/SamplingManager.py

@@ -0,0 +1,266 @@
+#
+# Autogenerated by Thrift Compiler (0.9.3)
+#
+# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+#
+#  options string: py:new_style,tornado
+#
+import six
+from six.moves import xrange
+
+from thrift.Thrift import TType, TMessageType, TException, TApplicationException
+import logging
+from .ttypes import *
+from thrift.Thrift import TProcessor
+from thrift.transport import TTransport
+from thrift.protocol import TBinaryProtocol, TProtocol
+try:
+  from thrift.protocol import fastbinary
+except:
+  fastbinary = None
+
+from tornado import gen
+from tornado import concurrent
+from thrift.transport import TTransport
+
+class Iface(object):
+  def getSamplingStrategy(self, serviceName):
+    """
+    Parameters:
+     - serviceName
+    """
+    pass
+
+
+class Client(Iface):
+  def __init__(self, transport, iprot_factory, oprot_factory=None):
+    self._transport = transport
+    self._iprot_factory = iprot_factory
+    self._oprot_factory = (oprot_factory if oprot_factory is not None
+                           else iprot_factory)
+    self._seqid = 0
+    self._reqs = {}
+    self._transport.io_loop.spawn_callback(self._start_receiving)
+
+  @gen.engine
+  def _start_receiving(self):
+    while True:
+      try:
+        frame = yield self._transport.readFrame()
+      except TTransport.TTransportException as e:
+        for future in self._reqs.itervalues():
+          future.set_exception(e)
+        self._reqs = {}
+        return
+      tr = TTransport.TMemoryBuffer(frame)
+      iprot = self._iprot_factory.getProtocol(tr)
+      (fname, mtype, rseqid) = iprot.readMessageBegin()
+      future = self._reqs.pop(rseqid, None)
+      if not future:
+        # future has already been discarded
+        continue
+      method = getattr(self, 'recv_' + fname)
+      try:
+        result = method(iprot, mtype, rseqid)
+      except Exception as e:
+        future.set_exception(e)
+      else:
+        future.set_result(result)
+
+  def getSamplingStrategy(self, serviceName):
+    """
+    Parameters:
+     - serviceName
+    """
+    self._seqid += 1
+    future = self._reqs[self._seqid] = concurrent.Future()
+    self.send_getSamplingStrategy(serviceName)
+    return future
+
+  def send_getSamplingStrategy(self, serviceName):
+    oprot = self._oprot_factory.getProtocol(self._transport)
+    oprot.writeMessageBegin('getSamplingStrategy', TMessageType.CALL, self._seqid)
+    args = getSamplingStrategy_args()
+    args.serviceName = serviceName
+    args.write(oprot)
+    oprot.writeMessageEnd()
+    oprot.trans.flush()
+
+  def recv_getSamplingStrategy(self, iprot, mtype, rseqid):
+    if mtype == TMessageType.EXCEPTION:
+      x = TApplicationException()
+      x.read(iprot)
+      iprot.readMessageEnd()
+      raise x
+    result = getSamplingStrategy_result()
+    result.read(iprot)
+    iprot.readMessageEnd()
+    if result.success is not None:
+      return result.success
+    raise TApplicationException(TApplicationException.MISSING_RESULT, "getSamplingStrategy failed: unknown result")
+
+
+class Processor(Iface, TProcessor):
+  def __init__(self, handler):
+    self._handler = handler
+    self._processMap = {}
+    self._processMap["getSamplingStrategy"] = Processor.process_getSamplingStrategy
+
+  def process(self, iprot, oprot):
+    (name, type, seqid) = iprot.readMessageBegin()
+    if name not in self._processMap:
+      iprot.skip(TType.STRUCT)
+      iprot.readMessageEnd()
+      x = TApplicationException(TApplicationException.UNKNOWN_METHOD, 'Unknown function %s' % (name))
+      oprot.writeMessageBegin(name, TMessageType.EXCEPTION, seqid)
+      x.write(oprot)
+      oprot.writeMessageEnd()
+      oprot.trans.flush()
+      return
+    else:
+      return self._processMap[name](self, seqid, iprot, oprot)
+
+  @gen.coroutine
+  def process_getSamplingStrategy(self, seqid, iprot, oprot):
+    args = getSamplingStrategy_args()
+    args.read(iprot)
+    iprot.readMessageEnd()
+    result = getSamplingStrategy_result()
+    result.success = yield gen.maybe_future(self._handler.getSamplingStrategy(args.serviceName))
+    oprot.writeMessageBegin("getSamplingStrategy", TMessageType.REPLY, seqid)
+    result.write(oprot)
+    oprot.writeMessageEnd()
+    oprot.trans.flush()
+
+
+# HELPER FUNCTIONS AND STRUCTURES
+
+class getSamplingStrategy_args(object):
+  """
+  Attributes:
+   - serviceName
+  """
+
+  thrift_spec = (
+    None, # 0
+    (1, TType.STRING, 'serviceName', None, None, ), # 1
+  )
+
+  def __init__(self, serviceName=None,):
+    self.serviceName = serviceName
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 1:
+        if ftype == TType.STRING:
+          self.serviceName = iprot.readString()
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('getSamplingStrategy_args')
+    if self.serviceName is not None:
+      oprot.writeFieldBegin('serviceName', TType.STRING, 1)
+      oprot.writeString(self.serviceName)
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.serviceName)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in six.iteritems(self.__dict__)]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)
+
+class getSamplingStrategy_result(object):
+  """
+  Attributes:
+   - success
+  """
+
+  thrift_spec = (
+    (0, TType.STRUCT, 'success', (SamplingStrategyResponse, SamplingStrategyResponse.thrift_spec), None, ), # 0
+  )
+
+  def __init__(self, success=None,):
+    self.success = success
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 0:
+        if ftype == TType.STRUCT:
+          self.success = SamplingStrategyResponse()
+          self.success.read(iprot)
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('getSamplingStrategy_result')
+    if self.success is not None:
+      oprot.writeFieldBegin('success', TType.STRUCT, 0)
+      self.success.write(oprot)
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.success)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in six.iteritems(self.__dict__)]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)

+ 1 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/sampling/__init__.py

@@ -0,0 +1 @@
+__all__ = ['ttypes', 'constants', 'SamplingManager']

+ 13 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/sampling/constants.py

@@ -0,0 +1,13 @@
+#
+# Autogenerated by Thrift Compiler (0.9.3)
+#
+# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+#
+#  options string: py:new_style,tornado
+#
+import six
+from six.moves import xrange
+
+from thrift.Thrift import TType, TMessageType, TException, TApplicationException
+from .ttypes import *
+

+ 479 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/sampling/ttypes.py

@@ -0,0 +1,479 @@
+#
+# Autogenerated by Thrift Compiler (0.9.3)
+#
+# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+#
+#  options string: py:new_style,tornado
+#
+import six
+from six.moves import xrange
+
+from thrift.Thrift import TType, TMessageType, TException, TApplicationException
+
+from thrift.transport import TTransport
+from thrift.protocol import TBinaryProtocol, TProtocol
+try:
+  from thrift.protocol import fastbinary
+except:
+  fastbinary = None
+
+
+class SamplingStrategyType(object):
+  PROBABILISTIC = 0
+  RATE_LIMITING = 1
+
+  _VALUES_TO_NAMES = {
+    0: "PROBABILISTIC",
+    1: "RATE_LIMITING",
+  }
+
+  _NAMES_TO_VALUES = {
+    "PROBABILISTIC": 0,
+    "RATE_LIMITING": 1,
+  }
+
+
+class ProbabilisticSamplingStrategy(object):
+  """
+  Attributes:
+   - samplingRate
+  """
+
+  thrift_spec = (
+    None, # 0
+    (1, TType.DOUBLE, 'samplingRate', None, None, ), # 1
+  )
+
+  def __init__(self, samplingRate=None,):
+    self.samplingRate = samplingRate
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 1:
+        if ftype == TType.DOUBLE:
+          self.samplingRate = iprot.readDouble()
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('ProbabilisticSamplingStrategy')
+    if self.samplingRate is not None:
+      oprot.writeFieldBegin('samplingRate', TType.DOUBLE, 1)
+      oprot.writeDouble(self.samplingRate)
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    if self.samplingRate is None:
+      raise TProtocol.TProtocolException(message='Required field samplingRate is unset!')
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.samplingRate)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in six.iteritems(self.__dict__)]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)
+
+class RateLimitingSamplingStrategy(object):
+  """
+  Attributes:
+   - maxTracesPerSecond
+  """
+
+  thrift_spec = (
+    None, # 0
+    (1, TType.I16, 'maxTracesPerSecond', None, None, ), # 1
+  )
+
+  def __init__(self, maxTracesPerSecond=None,):
+    self.maxTracesPerSecond = maxTracesPerSecond
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 1:
+        if ftype == TType.I16:
+          self.maxTracesPerSecond = iprot.readI16()
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('RateLimitingSamplingStrategy')
+    if self.maxTracesPerSecond is not None:
+      oprot.writeFieldBegin('maxTracesPerSecond', TType.I16, 1)
+      oprot.writeI16(self.maxTracesPerSecond)
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    if self.maxTracesPerSecond is None:
+      raise TProtocol.TProtocolException(message='Required field maxTracesPerSecond is unset!')
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.maxTracesPerSecond)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in six.iteritems(self.__dict__)]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)
+
+class OperationSamplingStrategy(object):
+  """
+  Attributes:
+   - operation
+   - probabilisticSampling
+  """
+
+  thrift_spec = (
+    None, # 0
+    (1, TType.STRING, 'operation', None, None, ), # 1
+    (2, TType.STRUCT, 'probabilisticSampling', (ProbabilisticSamplingStrategy, ProbabilisticSamplingStrategy.thrift_spec), None, ), # 2
+  )
+
+  def __init__(self, operation=None, probabilisticSampling=None,):
+    self.operation = operation
+    self.probabilisticSampling = probabilisticSampling
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 1:
+        if ftype == TType.STRING:
+          self.operation = iprot.readString()
+        else:
+          iprot.skip(ftype)
+      elif fid == 2:
+        if ftype == TType.STRUCT:
+          self.probabilisticSampling = ProbabilisticSamplingStrategy()
+          self.probabilisticSampling.read(iprot)
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('OperationSamplingStrategy')
+    if self.operation is not None:
+      oprot.writeFieldBegin('operation', TType.STRING, 1)
+      oprot.writeString(self.operation)
+      oprot.writeFieldEnd()
+    if self.probabilisticSampling is not None:
+      oprot.writeFieldBegin('probabilisticSampling', TType.STRUCT, 2)
+      self.probabilisticSampling.write(oprot)
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    if self.operation is None:
+      raise TProtocol.TProtocolException(message='Required field operation is unset!')
+    if self.probabilisticSampling is None:
+      raise TProtocol.TProtocolException(message='Required field probabilisticSampling is unset!')
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.operation)
+    value = (value * 31) ^ hash(self.probabilisticSampling)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in six.iteritems(self.__dict__)]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)
+
+class PerOperationSamplingStrategies(object):
+  """
+  Attributes:
+   - defaultSamplingProbability
+   - defaultLowerBoundTracesPerSecond
+   - perOperationStrategies
+   - defaultUpperBoundTracesPerSecond
+  """
+
+  thrift_spec = (
+    None, # 0
+    (1, TType.DOUBLE, 'defaultSamplingProbability', None, None, ), # 1
+    (2, TType.DOUBLE, 'defaultLowerBoundTracesPerSecond', None, None, ), # 2
+    (3, TType.LIST, 'perOperationStrategies', (TType.STRUCT,(OperationSamplingStrategy, OperationSamplingStrategy.thrift_spec)), None, ), # 3
+    (4, TType.DOUBLE, 'defaultUpperBoundTracesPerSecond', None, None, ), # 4
+  )
+
+  def __init__(self, defaultSamplingProbability=None, defaultLowerBoundTracesPerSecond=None, perOperationStrategies=None, defaultUpperBoundTracesPerSecond=None,):
+    self.defaultSamplingProbability = defaultSamplingProbability
+    self.defaultLowerBoundTracesPerSecond = defaultLowerBoundTracesPerSecond
+    self.perOperationStrategies = perOperationStrategies
+    self.defaultUpperBoundTracesPerSecond = defaultUpperBoundTracesPerSecond
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 1:
+        if ftype == TType.DOUBLE:
+          self.defaultSamplingProbability = iprot.readDouble()
+        else:
+          iprot.skip(ftype)
+      elif fid == 2:
+        if ftype == TType.DOUBLE:
+          self.defaultLowerBoundTracesPerSecond = iprot.readDouble()
+        else:
+          iprot.skip(ftype)
+      elif fid == 3:
+        if ftype == TType.LIST:
+          self.perOperationStrategies = []
+          (_etype3, _size0) = iprot.readListBegin()
+          for _i4 in xrange(_size0):
+            _elem5 = OperationSamplingStrategy()
+            _elem5.read(iprot)
+            self.perOperationStrategies.append(_elem5)
+          iprot.readListEnd()
+        else:
+          iprot.skip(ftype)
+      elif fid == 4:
+        if ftype == TType.DOUBLE:
+          self.defaultUpperBoundTracesPerSecond = iprot.readDouble()
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('PerOperationSamplingStrategies')
+    if self.defaultSamplingProbability is not None:
+      oprot.writeFieldBegin('defaultSamplingProbability', TType.DOUBLE, 1)
+      oprot.writeDouble(self.defaultSamplingProbability)
+      oprot.writeFieldEnd()
+    if self.defaultLowerBoundTracesPerSecond is not None:
+      oprot.writeFieldBegin('defaultLowerBoundTracesPerSecond', TType.DOUBLE, 2)
+      oprot.writeDouble(self.defaultLowerBoundTracesPerSecond)
+      oprot.writeFieldEnd()
+    if self.perOperationStrategies is not None:
+      oprot.writeFieldBegin('perOperationStrategies', TType.LIST, 3)
+      oprot.writeListBegin(TType.STRUCT, len(self.perOperationStrategies))
+      for iter6 in self.perOperationStrategies:
+        iter6.write(oprot)
+      oprot.writeListEnd()
+      oprot.writeFieldEnd()
+    if self.defaultUpperBoundTracesPerSecond is not None:
+      oprot.writeFieldBegin('defaultUpperBoundTracesPerSecond', TType.DOUBLE, 4)
+      oprot.writeDouble(self.defaultUpperBoundTracesPerSecond)
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    if self.defaultSamplingProbability is None:
+      raise TProtocol.TProtocolException(message='Required field defaultSamplingProbability is unset!')
+    if self.defaultLowerBoundTracesPerSecond is None:
+      raise TProtocol.TProtocolException(message='Required field defaultLowerBoundTracesPerSecond is unset!')
+    if self.perOperationStrategies is None:
+      raise TProtocol.TProtocolException(message='Required field perOperationStrategies is unset!')
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.defaultSamplingProbability)
+    value = (value * 31) ^ hash(self.defaultLowerBoundTracesPerSecond)
+    value = (value * 31) ^ hash(self.perOperationStrategies)
+    value = (value * 31) ^ hash(self.defaultUpperBoundTracesPerSecond)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in six.iteritems(self.__dict__)]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)
+
+class SamplingStrategyResponse(object):
+  """
+  Attributes:
+   - strategyType
+   - probabilisticSampling
+   - rateLimitingSampling
+   - operationSampling
+  """
+
+  thrift_spec = (
+    None, # 0
+    (1, TType.I32, 'strategyType', None, None, ), # 1
+    (2, TType.STRUCT, 'probabilisticSampling', (ProbabilisticSamplingStrategy, ProbabilisticSamplingStrategy.thrift_spec), None, ), # 2
+    (3, TType.STRUCT, 'rateLimitingSampling', (RateLimitingSamplingStrategy, RateLimitingSamplingStrategy.thrift_spec), None, ), # 3
+    (4, TType.STRUCT, 'operationSampling', (PerOperationSamplingStrategies, PerOperationSamplingStrategies.thrift_spec), None, ), # 4
+  )
+
+  def __init__(self, strategyType=None, probabilisticSampling=None, rateLimitingSampling=None, operationSampling=None,):
+    self.strategyType = strategyType
+    self.probabilisticSampling = probabilisticSampling
+    self.rateLimitingSampling = rateLimitingSampling
+    self.operationSampling = operationSampling
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 1:
+        if ftype == TType.I32:
+          self.strategyType = iprot.readI32()
+        else:
+          iprot.skip(ftype)
+      elif fid == 2:
+        if ftype == TType.STRUCT:
+          self.probabilisticSampling = ProbabilisticSamplingStrategy()
+          self.probabilisticSampling.read(iprot)
+        else:
+          iprot.skip(ftype)
+      elif fid == 3:
+        if ftype == TType.STRUCT:
+          self.rateLimitingSampling = RateLimitingSamplingStrategy()
+          self.rateLimitingSampling.read(iprot)
+        else:
+          iprot.skip(ftype)
+      elif fid == 4:
+        if ftype == TType.STRUCT:
+          self.operationSampling = PerOperationSamplingStrategies()
+          self.operationSampling.read(iprot)
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('SamplingStrategyResponse')
+    if self.strategyType is not None:
+      oprot.writeFieldBegin('strategyType', TType.I32, 1)
+      oprot.writeI32(self.strategyType)
+      oprot.writeFieldEnd()
+    if self.probabilisticSampling is not None:
+      oprot.writeFieldBegin('probabilisticSampling', TType.STRUCT, 2)
+      self.probabilisticSampling.write(oprot)
+      oprot.writeFieldEnd()
+    if self.rateLimitingSampling is not None:
+      oprot.writeFieldBegin('rateLimitingSampling', TType.STRUCT, 3)
+      self.rateLimitingSampling.write(oprot)
+      oprot.writeFieldEnd()
+    if self.operationSampling is not None:
+      oprot.writeFieldBegin('operationSampling', TType.STRUCT, 4)
+      self.operationSampling.write(oprot)
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    if self.strategyType is None:
+      raise TProtocol.TProtocolException(message='Required field strategyType is unset!')
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.strategyType)
+    value = (value * 31) ^ hash(self.probabilisticSampling)
+    value = (value * 31) ^ hash(self.rateLimitingSampling)
+    value = (value * 31) ^ hash(self.operationSampling)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in six.iteritems(self.__dict__)]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)

+ 283 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/zipkincore/ZipkinCollector.py

@@ -0,0 +1,283 @@
+#
+# Autogenerated by Thrift Compiler (0.9.3)
+#
+# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+#
+#  options string: py:new_style,tornado
+#
+import six
+from six.moves import xrange
+
+from thrift.Thrift import TType, TMessageType, TException, TApplicationException
+import logging
+from .ttypes import *
+from thrift.Thrift import TProcessor
+from thrift.transport import TTransport
+from thrift.protocol import TBinaryProtocol, TProtocol
+try:
+  from thrift.protocol import fastbinary
+except:
+  fastbinary = None
+
+from tornado import gen
+from tornado import concurrent
+from thrift.transport import TTransport
+
+class Iface(object):
+  def submitZipkinBatch(self, spans):
+    """
+    Parameters:
+     - spans
+    """
+    pass
+
+
+class Client(Iface):
+  def __init__(self, transport, iprot_factory, oprot_factory=None):
+    self._transport = transport
+    self._iprot_factory = iprot_factory
+    self._oprot_factory = (oprot_factory if oprot_factory is not None
+                           else iprot_factory)
+    self._seqid = 0
+    self._reqs = {}
+    self._transport.io_loop.spawn_callback(self._start_receiving)
+
+  @gen.engine
+  def _start_receiving(self):
+    while True:
+      try:
+        frame = yield self._transport.readFrame()
+      except TTransport.TTransportException as e:
+        for future in self._reqs.itervalues():
+          future.set_exception(e)
+        self._reqs = {}
+        return
+      tr = TTransport.TMemoryBuffer(frame)
+      iprot = self._iprot_factory.getProtocol(tr)
+      (fname, mtype, rseqid) = iprot.readMessageBegin()
+      future = self._reqs.pop(rseqid, None)
+      if not future:
+        # future has already been discarded
+        continue
+      method = getattr(self, 'recv_' + fname)
+      try:
+        result = method(iprot, mtype, rseqid)
+      except Exception as e:
+        future.set_exception(e)
+      else:
+        future.set_result(result)
+
+  def submitZipkinBatch(self, spans):
+    """
+    Parameters:
+     - spans
+    """
+    self._seqid += 1
+    future = self._reqs[self._seqid] = concurrent.Future()
+    self.send_submitZipkinBatch(spans)
+    return future
+
+  def send_submitZipkinBatch(self, spans):
+    oprot = self._oprot_factory.getProtocol(self._transport)
+    oprot.writeMessageBegin('submitZipkinBatch', TMessageType.CALL, self._seqid)
+    args = submitZipkinBatch_args()
+    args.spans = spans
+    args.write(oprot)
+    oprot.writeMessageEnd()
+    oprot.trans.flush()
+
+  def recv_submitZipkinBatch(self, iprot, mtype, rseqid):
+    if mtype == TMessageType.EXCEPTION:
+      x = TApplicationException()
+      x.read(iprot)
+      iprot.readMessageEnd()
+      raise x
+    result = submitZipkinBatch_result()
+    result.read(iprot)
+    iprot.readMessageEnd()
+    if result.success is not None:
+      return result.success
+    raise TApplicationException(TApplicationException.MISSING_RESULT, "submitZipkinBatch failed: unknown result")
+
+
+class Processor(Iface, TProcessor):
+  def __init__(self, handler):
+    self._handler = handler
+    self._processMap = {}
+    self._processMap["submitZipkinBatch"] = Processor.process_submitZipkinBatch
+
+  def process(self, iprot, oprot):
+    (name, type, seqid) = iprot.readMessageBegin()
+    if name not in self._processMap:
+      iprot.skip(TType.STRUCT)
+      iprot.readMessageEnd()
+      x = TApplicationException(TApplicationException.UNKNOWN_METHOD, 'Unknown function %s' % (name))
+      oprot.writeMessageBegin(name, TMessageType.EXCEPTION, seqid)
+      x.write(oprot)
+      oprot.writeMessageEnd()
+      oprot.trans.flush()
+      return
+    else:
+      return self._processMap[name](self, seqid, iprot, oprot)
+
+  @gen.coroutine
+  def process_submitZipkinBatch(self, seqid, iprot, oprot):
+    args = submitZipkinBatch_args()
+    args.read(iprot)
+    iprot.readMessageEnd()
+    result = submitZipkinBatch_result()
+    result.success = yield gen.maybe_future(self._handler.submitZipkinBatch(args.spans))
+    oprot.writeMessageBegin("submitZipkinBatch", TMessageType.REPLY, seqid)
+    result.write(oprot)
+    oprot.writeMessageEnd()
+    oprot.trans.flush()
+
+
+# HELPER FUNCTIONS AND STRUCTURES
+
+class submitZipkinBatch_args(object):
+  """
+  Attributes:
+   - spans
+  """
+
+  thrift_spec = (
+    None, # 0
+    (1, TType.LIST, 'spans', (TType.STRUCT,(Span, Span.thrift_spec)), None, ), # 1
+  )
+
+  def __init__(self, spans=None,):
+    self.spans = spans
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 1:
+        if ftype == TType.LIST:
+          self.spans = []
+          (_etype17, _size14) = iprot.readListBegin()
+          for _i18 in xrange(_size14):
+            _elem19 = Span()
+            _elem19.read(iprot)
+            self.spans.append(_elem19)
+          iprot.readListEnd()
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('submitZipkinBatch_args')
+    if self.spans is not None:
+      oprot.writeFieldBegin('spans', TType.LIST, 1)
+      oprot.writeListBegin(TType.STRUCT, len(self.spans))
+      for iter20 in self.spans:
+        iter20.write(oprot)
+      oprot.writeListEnd()
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.spans)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in six.iteritems(self.__dict__)]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)
+
+class submitZipkinBatch_result(object):
+  """
+  Attributes:
+   - success
+  """
+
+  thrift_spec = (
+    (0, TType.LIST, 'success', (TType.STRUCT,(Response, Response.thrift_spec)), None, ), # 0
+  )
+
+  def __init__(self, success=None,):
+    self.success = success
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 0:
+        if ftype == TType.LIST:
+          self.success = []
+          (_etype24, _size21) = iprot.readListBegin()
+          for _i25 in xrange(_size21):
+            _elem26 = Response()
+            _elem26.read(iprot)
+            self.success.append(_elem26)
+          iprot.readListEnd()
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('submitZipkinBatch_result')
+    if self.success is not None:
+      oprot.writeFieldBegin('success', TType.LIST, 0)
+      oprot.writeListBegin(TType.STRUCT, len(self.success))
+      for iter27 in self.success:
+        iter27.write(oprot)
+      oprot.writeListEnd()
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.success)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in six.iteritems(self.__dict__)]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)

+ 1 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/zipkincore/__init__.py

@@ -0,0 +1 @@
+__all__ = ['ttypes', 'constants', 'ZipkinCollector']

+ 29 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/zipkincore/constants.py

@@ -0,0 +1,29 @@
+#
+# Autogenerated by Thrift Compiler (0.9.3)
+#
+# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+#
+#  options string: py:new_style,tornado
+#
+import six
+from six.moves import xrange
+
+from thrift.Thrift import TType, TMessageType, TException, TApplicationException
+from .ttypes import *
+
+CLIENT_SEND = "cs"
+CLIENT_RECV = "cr"
+SERVER_SEND = "ss"
+SERVER_RECV = "sr"
+MESSAGE_SEND = "ms"
+MESSAGE_RECV = "mr"
+WIRE_SEND = "ws"
+WIRE_RECV = "wr"
+CLIENT_SEND_FRAGMENT = "csf"
+CLIENT_RECV_FRAGMENT = "crf"
+SERVER_SEND_FRAGMENT = "ssf"
+SERVER_RECV_FRAGMENT = "srf"
+LOCAL_COMPONENT = "lc"
+CLIENT_ADDR = "ca"
+SERVER_ADDR = "sa"
+MESSAGE_ADDR = "ma"

+ 695 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/thrift_gen/zipkincore/ttypes.py

@@ -0,0 +1,695 @@
+#
+# Autogenerated by Thrift Compiler (0.9.3)
+#
+# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+#
+#  options string: py:new_style,tornado
+#
+import six
+from six.moves import xrange
+
+from thrift.Thrift import TType, TMessageType, TException, TApplicationException
+
+from thrift.transport import TTransport
+from thrift.protocol import TBinaryProtocol, TProtocol
+try:
+  from thrift.protocol import fastbinary
+except:
+  fastbinary = None
+
+
+class AnnotationType(object):
+  BOOL = 0
+  BYTES = 1
+  I16 = 2
+  I32 = 3
+  I64 = 4
+  DOUBLE = 5
+  STRING = 6
+
+  _VALUES_TO_NAMES = {
+    0: "BOOL",
+    1: "BYTES",
+    2: "I16",
+    3: "I32",
+    4: "I64",
+    5: "DOUBLE",
+    6: "STRING",
+  }
+
+  _NAMES_TO_VALUES = {
+    "BOOL": 0,
+    "BYTES": 1,
+    "I16": 2,
+    "I32": 3,
+    "I64": 4,
+    "DOUBLE": 5,
+    "STRING": 6,
+  }
+
+
+class Endpoint(object):
+  """
+  Indicates the network context of a service recording an annotation with two
+  exceptions.
+
+  When a BinaryAnnotation, and key is CLIENT_ADDR or SERVER_ADDR,
+  the endpoint indicates the source or destination of an RPC. This exception
+  allows zipkin to display network context of uninstrumented services, or
+  clients such as web browsers.
+
+  Attributes:
+   - ipv4: IPv4 host address packed into 4 bytes.
+
+  Ex for the ip 1.2.3.4, it would be (1 << 24) | (2 << 16) | (3 << 8) | 4
+   - port: IPv4 port
+
+  Note: this is to be treated as an unsigned integer, so watch for negatives.
+
+  Conventionally, when the port isn't known, port = 0.
+   - service_name: Service name in lowercase, such as "memcache" or "zipkin-web"
+
+  Conventionally, when the service name isn't known, service_name = "unknown".
+   - ipv6: IPv6 host address packed into 16 bytes. Ex Inet6Address.getBytes()
+  """
+
+  thrift_spec = (
+    None, # 0
+    (1, TType.I32, 'ipv4', None, None, ), # 1
+    (2, TType.I16, 'port', None, None, ), # 2
+    (3, TType.STRING, 'service_name', None, None, ), # 3
+    (4, TType.STRING, 'ipv6', None, None, ), # 4
+  )
+
+  def __init__(self, ipv4=None, port=None, service_name=None, ipv6=None,):
+    self.ipv4 = ipv4
+    self.port = port
+    self.service_name = service_name
+    self.ipv6 = ipv6
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 1:
+        if ftype == TType.I32:
+          self.ipv4 = iprot.readI32()
+        else:
+          iprot.skip(ftype)
+      elif fid == 2:
+        if ftype == TType.I16:
+          self.port = iprot.readI16()
+        else:
+          iprot.skip(ftype)
+      elif fid == 3:
+        if ftype == TType.STRING:
+          self.service_name = iprot.readString()
+        else:
+          iprot.skip(ftype)
+      elif fid == 4:
+        if ftype == TType.STRING:
+          self.ipv6 = iprot.readString()
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('Endpoint')
+    if self.ipv4 is not None:
+      oprot.writeFieldBegin('ipv4', TType.I32, 1)
+      oprot.writeI32(self.ipv4)
+      oprot.writeFieldEnd()
+    if self.port is not None:
+      oprot.writeFieldBegin('port', TType.I16, 2)
+      oprot.writeI16(self.port)
+      oprot.writeFieldEnd()
+    if self.service_name is not None:
+      oprot.writeFieldBegin('service_name', TType.STRING, 3)
+      oprot.writeString(self.service_name)
+      oprot.writeFieldEnd()
+    if self.ipv6 is not None:
+      oprot.writeFieldBegin('ipv6', TType.STRING, 4)
+      oprot.writeString(self.ipv6)
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.ipv4)
+    value = (value * 31) ^ hash(self.port)
+    value = (value * 31) ^ hash(self.service_name)
+    value = (value * 31) ^ hash(self.ipv6)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in six.iteritems(self.__dict__)]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)
+
+class Annotation(object):
+  """
+  An annotation is similar to a log statement. It includes a host field which
+  allows these events to be attributed properly, and also aggregatable.
+
+  Attributes:
+   - timestamp: Microseconds from epoch.
+
+  This value should use the most precise value possible. For example,
+  gettimeofday or syncing nanoTime against a tick of currentTimeMillis.
+   - value
+   - host: Always the host that recorded the event. By specifying the host you allow
+  rollup of all events (such as client requests to a service) by IP address.
+  """
+
+  thrift_spec = (
+    None, # 0
+    (1, TType.I64, 'timestamp', None, None, ), # 1
+    (2, TType.STRING, 'value', None, None, ), # 2
+    (3, TType.STRUCT, 'host', (Endpoint, Endpoint.thrift_spec), None, ), # 3
+  )
+
+  def __init__(self, timestamp=None, value=None, host=None,):
+    self.timestamp = timestamp
+    self.value = value
+    self.host = host
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 1:
+        if ftype == TType.I64:
+          self.timestamp = iprot.readI64()
+        else:
+          iprot.skip(ftype)
+      elif fid == 2:
+        if ftype == TType.STRING:
+          self.value = iprot.readString()
+        else:
+          iprot.skip(ftype)
+      elif fid == 3:
+        if ftype == TType.STRUCT:
+          self.host = Endpoint()
+          self.host.read(iprot)
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('Annotation')
+    if self.timestamp is not None:
+      oprot.writeFieldBegin('timestamp', TType.I64, 1)
+      oprot.writeI64(self.timestamp)
+      oprot.writeFieldEnd()
+    if self.value is not None:
+      oprot.writeFieldBegin('value', TType.STRING, 2)
+      oprot.writeString(self.value)
+      oprot.writeFieldEnd()
+    if self.host is not None:
+      oprot.writeFieldBegin('host', TType.STRUCT, 3)
+      self.host.write(oprot)
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.timestamp)
+    value = (value * 31) ^ hash(self.value)
+    value = (value * 31) ^ hash(self.host)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in six.iteritems(self.__dict__)]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)
+
+class BinaryAnnotation(object):
+  """
+  Binary annotations are tags applied to a Span to give it context. For
+  example, a binary annotation of "http.uri" could the path to a resource in a
+  RPC call.
+
+  Binary annotations of type STRING are always queryable, though more a
+  historical implementation detail than a structural concern.
+
+  Binary annotations can repeat, and vary on the host. Similar to Annotation,
+  the host indicates who logged the event. This allows you to tell the
+  difference between the client and server side of the same key. For example,
+  the key "http.uri" might be different on the client and server side due to
+  rewriting, like "/api/v1/myresource" vs "/myresource. Via the host field,
+  you can see the different points of view, which often help in debugging.
+
+  Attributes:
+   - key
+   - value
+   - annotation_type
+   - host: The host that recorded tag, which allows you to differentiate between
+  multiple tags with the same key. There are two exceptions to this.
+
+  When the key is CLIENT_ADDR or SERVER_ADDR, host indicates the source or
+  destination of an RPC. This exception allows zipkin to display network
+  context of uninstrumented services, or clients such as web browsers.
+  """
+
+  thrift_spec = (
+    None, # 0
+    (1, TType.STRING, 'key', None, None, ), # 1
+    (2, TType.STRING, 'value', None, None, ), # 2
+    (3, TType.I32, 'annotation_type', None, None, ), # 3
+    (4, TType.STRUCT, 'host', (Endpoint, Endpoint.thrift_spec), None, ), # 4
+  )
+
+  def __init__(self, key=None, value=None, annotation_type=None, host=None,):
+    self.key = key
+    self.value = value
+    self.annotation_type = annotation_type
+    self.host = host
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 1:
+        if ftype == TType.STRING:
+          self.key = iprot.readString()
+        else:
+          iprot.skip(ftype)
+      elif fid == 2:
+        if ftype == TType.STRING:
+          self.value = iprot.readString()
+        else:
+          iprot.skip(ftype)
+      elif fid == 3:
+        if ftype == TType.I32:
+          self.annotation_type = iprot.readI32()
+        else:
+          iprot.skip(ftype)
+      elif fid == 4:
+        if ftype == TType.STRUCT:
+          self.host = Endpoint()
+          self.host.read(iprot)
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('BinaryAnnotation')
+    if self.key is not None:
+      oprot.writeFieldBegin('key', TType.STRING, 1)
+      oprot.writeString(self.key)
+      oprot.writeFieldEnd()
+    if self.value is not None:
+      oprot.writeFieldBegin('value', TType.STRING, 2)
+      oprot.writeString(self.value)
+      oprot.writeFieldEnd()
+    if self.annotation_type is not None:
+      oprot.writeFieldBegin('annotation_type', TType.I32, 3)
+      oprot.writeI32(self.annotation_type)
+      oprot.writeFieldEnd()
+    if self.host is not None:
+      oprot.writeFieldBegin('host', TType.STRUCT, 4)
+      self.host.write(oprot)
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.key)
+    value = (value * 31) ^ hash(self.value)
+    value = (value * 31) ^ hash(self.annotation_type)
+    value = (value * 31) ^ hash(self.host)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in six.iteritems(self.__dict__)]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)
+
+class Span(object):
+  """
+  A trace is a series of spans (often RPC calls) which form a latency tree.
+
+  The root span is where trace_id = id and parent_id = Nil. The root span is
+  usually the longest interval in the trace, starting with a SERVER_RECV
+  annotation and ending with a SERVER_SEND.
+
+  Attributes:
+   - trace_id
+   - name: Span name in lowercase, rpc method for example
+
+  Conventionally, when the span name isn't known, name = "unknown".
+   - id
+   - parent_id
+   - annotations
+   - binary_annotations
+   - debug
+   - timestamp: Microseconds from epoch of the creation of this span.
+
+  This value should be set directly by instrumentation, using the most
+  precise value possible. For example, gettimeofday or syncing nanoTime
+  against a tick of currentTimeMillis.
+
+  For compatibilty with instrumentation that precede this field, collectors
+  or span stores can derive this via Annotation.timestamp.
+  For example, SERVER_RECV.timestamp or CLIENT_SEND.timestamp.
+
+  This field is optional for compatibility with old data: first-party span
+  stores are expected to support this at time of introduction.
+   - duration: Measurement of duration in microseconds, used to support queries.
+
+  This value should be set directly, where possible. Doing so encourages
+  precise measurement decoupled from problems of clocks, such as skew or NTP
+  updates causing time to move backwards.
+
+  For compatibilty with instrumentation that precede this field, collectors
+  or span stores can derive this by subtracting Annotation.timestamp.
+  For example, SERVER_SEND.timestamp - SERVER_RECV.timestamp.
+
+  If this field is persisted as unset, zipkin will continue to work, except
+  duration query support will be implementation-specific. Similarly, setting
+  this field non-atomically is implementation-specific.
+
+  This field is i64 vs i32 to support spans longer than 35 minutes.
+   - trace_id_high: Optional unique 8-byte additional identifier for a trace. If non zero, this
+  means the trace uses 128 bit traceIds instead of 64 bit.
+  """
+
+  thrift_spec = (
+    None, # 0
+    (1, TType.I64, 'trace_id', None, None, ), # 1
+    None, # 2
+    (3, TType.STRING, 'name', None, None, ), # 3
+    (4, TType.I64, 'id', None, None, ), # 4
+    (5, TType.I64, 'parent_id', None, None, ), # 5
+    (6, TType.LIST, 'annotations', (TType.STRUCT,(Annotation, Annotation.thrift_spec)), None, ), # 6
+    None, # 7
+    (8, TType.LIST, 'binary_annotations', (TType.STRUCT,(BinaryAnnotation, BinaryAnnotation.thrift_spec)), None, ), # 8
+    (9, TType.BOOL, 'debug', None, False, ), # 9
+    (10, TType.I64, 'timestamp', None, None, ), # 10
+    (11, TType.I64, 'duration', None, None, ), # 11
+    (12, TType.I64, 'trace_id_high', None, None, ), # 12
+  )
+
+  def __init__(self, trace_id=None, name=None, id=None, parent_id=None, annotations=None, binary_annotations=None, debug=thrift_spec[9][4], timestamp=None, duration=None, trace_id_high=None,):
+    self.trace_id = trace_id
+    self.name = name
+    self.id = id
+    self.parent_id = parent_id
+    self.annotations = annotations
+    self.binary_annotations = binary_annotations
+    self.debug = debug
+    self.timestamp = timestamp
+    self.duration = duration
+    self.trace_id_high = trace_id_high
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 1:
+        if ftype == TType.I64:
+          self.trace_id = iprot.readI64()
+        else:
+          iprot.skip(ftype)
+      elif fid == 3:
+        if ftype == TType.STRING:
+          self.name = iprot.readString()
+        else:
+          iprot.skip(ftype)
+      elif fid == 4:
+        if ftype == TType.I64:
+          self.id = iprot.readI64()
+        else:
+          iprot.skip(ftype)
+      elif fid == 5:
+        if ftype == TType.I64:
+          self.parent_id = iprot.readI64()
+        else:
+          iprot.skip(ftype)
+      elif fid == 6:
+        if ftype == TType.LIST:
+          self.annotations = []
+          (_etype3, _size0) = iprot.readListBegin()
+          for _i4 in xrange(_size0):
+            _elem5 = Annotation()
+            _elem5.read(iprot)
+            self.annotations.append(_elem5)
+          iprot.readListEnd()
+        else:
+          iprot.skip(ftype)
+      elif fid == 8:
+        if ftype == TType.LIST:
+          self.binary_annotations = []
+          (_etype9, _size6) = iprot.readListBegin()
+          for _i10 in xrange(_size6):
+            _elem11 = BinaryAnnotation()
+            _elem11.read(iprot)
+            self.binary_annotations.append(_elem11)
+          iprot.readListEnd()
+        else:
+          iprot.skip(ftype)
+      elif fid == 9:
+        if ftype == TType.BOOL:
+          self.debug = iprot.readBool()
+        else:
+          iprot.skip(ftype)
+      elif fid == 10:
+        if ftype == TType.I64:
+          self.timestamp = iprot.readI64()
+        else:
+          iprot.skip(ftype)
+      elif fid == 11:
+        if ftype == TType.I64:
+          self.duration = iprot.readI64()
+        else:
+          iprot.skip(ftype)
+      elif fid == 12:
+        if ftype == TType.I64:
+          self.trace_id_high = iprot.readI64()
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('Span')
+    if self.trace_id is not None:
+      oprot.writeFieldBegin('trace_id', TType.I64, 1)
+      oprot.writeI64(self.trace_id)
+      oprot.writeFieldEnd()
+    if self.name is not None:
+      oprot.writeFieldBegin('name', TType.STRING, 3)
+      oprot.writeString(self.name)
+      oprot.writeFieldEnd()
+    if self.id is not None:
+      oprot.writeFieldBegin('id', TType.I64, 4)
+      oprot.writeI64(self.id)
+      oprot.writeFieldEnd()
+    if self.parent_id is not None:
+      oprot.writeFieldBegin('parent_id', TType.I64, 5)
+      oprot.writeI64(self.parent_id)
+      oprot.writeFieldEnd()
+    if self.annotations is not None:
+      oprot.writeFieldBegin('annotations', TType.LIST, 6)
+      oprot.writeListBegin(TType.STRUCT, len(self.annotations))
+      for iter12 in self.annotations:
+        iter12.write(oprot)
+      oprot.writeListEnd()
+      oprot.writeFieldEnd()
+    if self.binary_annotations is not None:
+      oprot.writeFieldBegin('binary_annotations', TType.LIST, 8)
+      oprot.writeListBegin(TType.STRUCT, len(self.binary_annotations))
+      for iter13 in self.binary_annotations:
+        iter13.write(oprot)
+      oprot.writeListEnd()
+      oprot.writeFieldEnd()
+    if self.debug is not None:
+      oprot.writeFieldBegin('debug', TType.BOOL, 9)
+      oprot.writeBool(self.debug)
+      oprot.writeFieldEnd()
+    if self.timestamp is not None:
+      oprot.writeFieldBegin('timestamp', TType.I64, 10)
+      oprot.writeI64(self.timestamp)
+      oprot.writeFieldEnd()
+    if self.duration is not None:
+      oprot.writeFieldBegin('duration', TType.I64, 11)
+      oprot.writeI64(self.duration)
+      oprot.writeFieldEnd()
+    if self.trace_id_high is not None:
+      oprot.writeFieldBegin('trace_id_high', TType.I64, 12)
+      oprot.writeI64(self.trace_id_high)
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.trace_id)
+    value = (value * 31) ^ hash(self.name)
+    value = (value * 31) ^ hash(self.id)
+    value = (value * 31) ^ hash(self.parent_id)
+    value = (value * 31) ^ hash(self.annotations)
+    value = (value * 31) ^ hash(self.binary_annotations)
+    value = (value * 31) ^ hash(self.debug)
+    value = (value * 31) ^ hash(self.timestamp)
+    value = (value * 31) ^ hash(self.duration)
+    value = (value * 31) ^ hash(self.trace_id_high)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in six.iteritems(self.__dict__)]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)
+
+class Response(object):
+  """
+  Attributes:
+   - ok
+  """
+
+  thrift_spec = (
+    None, # 0
+    (1, TType.BOOL, 'ok', None, None, ), # 1
+  )
+
+  def __init__(self, ok=None,):
+    self.ok = ok
+
+  def read(self, iprot):
+    if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
+      fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
+      return
+    iprot.readStructBegin()
+    while True:
+      (fname, ftype, fid) = iprot.readFieldBegin()
+      if ftype == TType.STOP:
+        break
+      if fid == 1:
+        if ftype == TType.BOOL:
+          self.ok = iprot.readBool()
+        else:
+          iprot.skip(ftype)
+      else:
+        iprot.skip(ftype)
+      iprot.readFieldEnd()
+    iprot.readStructEnd()
+
+  def write(self, oprot):
+    if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
+      oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+      return
+    oprot.writeStructBegin('Response')
+    if self.ok is not None:
+      oprot.writeFieldBegin('ok', TType.BOOL, 1)
+      oprot.writeBool(self.ok)
+      oprot.writeFieldEnd()
+    oprot.writeFieldStop()
+    oprot.writeStructEnd()
+
+  def validate(self):
+    if self.ok is None:
+      raise TProtocol.TProtocolException(message='Required field ok is unset!')
+    return
+
+
+  def __hash__(self):
+    value = 17
+    value = (value * 31) ^ hash(self.ok)
+    return value
+
+  def __repr__(self):
+    L = ['%s=%r' % (key, value)
+      for key, value in six.iteritems(self.__dict__)]
+    return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+  def __eq__(self, other):
+    return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+  def __ne__(self, other):
+    return not (self == other)

+ 198 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/throttler.py

@@ -0,0 +1,198 @@
+# Copyright (c) 2018 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import, division
+import json
+import logging
+import random
+from threading import Lock
+
+from tornado.ioloop import PeriodicCallback
+
+from .constants import DEFAULT_THROTTLER_REFRESH_INTERVAL
+from .metrics import Metrics, MetricsFactory
+from .utils import ErrorReporter
+
+MINIMUM_CREDITS = 1.0
+default_logger = logging.getLogger('jaeger_tracing')
+
+
+class RemoteThrottler(object):
+    """
+    RemoteThrottler controls the flow of spans emitted from client to prevent
+    flooding. RemoteThrottler requests credits from the throttling service
+    periodically. These credits determine the amount of debug spans a client
+    may emit for a particular operation without receiving more credits.
+    :param channel: channel for communicating with jaeger-agent
+    :param service_name: name of this application
+    :param kwargs: optional parameters
+        - refresh_interval: interval in seconds for requesting more credits
+        - logger: Logger instance
+        - metrics_factory: factory to create throttler-specific metrics
+        - error_reporter: ErrorReporter instance
+    """
+
+    def __init__(self, channel, service_name, **kwargs):
+        self.channel = channel
+        self.service_name = service_name
+        self.client_id = None
+        self.refresh_interval = \
+            kwargs.get('refresh_interval', DEFAULT_THROTTLER_REFRESH_INTERVAL)
+        self.logger = kwargs.get('logger', default_logger)
+        metrics_factory = kwargs.get('metrics_factory', MetricsFactory())
+        self.metrics = ThrottlerMetrics(metrics_factory)
+        self.error_reporter = kwargs.get('error_reporter', ErrorReporter(Metrics()))
+        self.credits = {}
+        self.lock = Lock()
+        self.running = True
+        self.periodic = None
+
+        if not self.channel.io_loop:
+            self.logger.error(
+                'Cannot acquire IOLoop, throttler will not be updated')
+        else:
+            self.channel.io_loop.add_callback(self._init_polling)
+
+    def is_allowed(self, operation):
+        with self.lock:
+            if operation not in self.credits:
+                self.credits[operation] = 0.0
+                self.metrics.throttled_debug_spans(1)
+                return False
+            value = self.credits[operation]
+            if value < MINIMUM_CREDITS:
+                self.metrics.throttled_debug_spans(1)
+                return False
+
+            self.credits[operation] = value - MINIMUM_CREDITS
+            return True
+
+    def _set_client_id(self, client_id):
+        """
+        Method for tracer to set client ID of throttler.
+        """
+        with self.lock:
+            if self.client_id is None:
+                self.client_id = client_id
+
+    def _init_polling(self):
+        """
+        Bootstrap polling for throttler.
+
+        To avoid spiky traffic from throttler clients, we use a random delay
+        before the first poll.
+        """
+        with self.lock:
+            if not self.running:
+                return
+
+            r = random.Random()
+            delay = r.random() * self.refresh_interval
+            self.channel.io_loop.call_later(
+                delay=delay, callback=self._delayed_polling)
+            self.logger.info(
+                'Delaying throttling credit polling by %d sec', delay)
+
+    def _operations(self):
+        with self.lock:
+            return self.credits.keys()
+
+    def _delayed_polling(self):
+        def callback():
+            self._fetch_credits(self._operations())
+
+        periodic = PeriodicCallback(
+            callback=callback,
+            # convert interval to milliseconds
+            callback_time=self.refresh_interval * 1000,
+            io_loop=self.channel.io_loop)
+        self._fetch_credits(self._operations())
+        with self.lock:
+            if not self.running:
+                return
+            self.periodic = periodic
+            self.periodic.start()
+            self.logger.info(
+                'Throttling client started with refresh interval %d sec',
+                self.refresh_interval)
+
+    def _fetch_credits(self, operations):
+        if not operations:
+            return
+        self.logger.debug('Requesting throttling credits')
+        fut = self.channel.request_throttling_credits(
+            self.service_name, self.client_id, operations)
+        fut.add_done_callback(self._request_callback)
+
+    def _request_callback(self, future):
+        exception = future.exception()
+        if exception:
+            self.metrics.throttler_update_failure(1)
+            self.error_reporter.error(
+                'Failed to get throttling credits from jaeger-agent: %s',
+                exception)
+            return
+
+        response = future.result()
+        # In Python 3.5 response.body is of type bytes and json.loads() does only support str
+        # See: https://github.com/jaegertracing/jaeger-client-python/issues/180
+        if hasattr(response.body, 'decode') and callable(response.body.decode):
+            response_body = response.body.decode('utf-8')
+        else:
+            response_body = response.body
+
+        try:
+            throttling_response = json.loads(response_body)
+            self.logger.debug('Received throttling response: %s',
+                              throttling_response)
+            self._update_credits(throttling_response)
+            self.metrics.throttler_update_success(1)
+        except Exception as e:
+            self.metrics.throttler_update_failure(1)
+            self.error_reporter.error(
+                'Failed to parse throttling credits response '
+                'from jaeger-agent: %s [%s]', e, response_body)
+            return
+
+    def _update_credits(self, response):
+        with self.lock:
+            for op_balance in response['balances']:
+                op = op_balance['operation']
+                balance = op_balance['balance']
+                if op not in self.credits:
+                    self.credits[op] = 0
+                self.credits[op] += balance
+            self.logger.debug('credits = %s', self.credits)
+
+    def close(self):
+        with self.lock:
+            self.running = False
+            if self.periodic:
+                self.periodic.stop()
+
+
+class ThrottlerMetrics(object):
+    """
+    Metrics specific to throttler.
+    """
+
+    def __init__(self, metrics_factory):
+        self.throttled_debug_spans = \
+            metrics_factory.create_counter(name='jaeger:throttled_debug_spans')
+        self.throttler_update_success = \
+            metrics_factory.create_counter(name='jaeger:throttler_update',
+                                           tags={'result': 'ok'})
+        self.throttler_update_failure = \
+            metrics_factory.create_counter(name='jaeger:throttler_update',
+                                           tags={'result': 'err'})

+ 342 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/tracer.py

@@ -0,0 +1,342 @@
+# Copyright (c) 2016-2018 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+
+import socket
+
+import logging
+import os
+import random
+import sys
+import time
+import six
+import opentracing
+from opentracing import Format, UnsupportedFormatException
+from opentracing.ext import tags as ext_tags
+from opentracing.scope_managers import ThreadLocalScopeManager
+
+from . import constants
+from .codecs import TextCodec, ZipkinCodec, ZipkinSpanFormat, BinaryCodec
+from .span import Span, SAMPLED_FLAG, DEBUG_FLAG
+from .span_context import SpanContext
+from .metrics import Metrics, LegacyMetricsFactory
+from .utils import local_ip
+
+logger = logging.getLogger('jaeger_tracing')
+
+
+class Tracer(opentracing.Tracer):
+    """
+    N.B. metrics has been deprecated, use metrics_factory instead.
+    """
+    def __init__(
+        self, service_name, reporter, sampler, metrics=None,
+        metrics_factory=None,
+        trace_id_header=constants.TRACE_ID_HEADER,
+        generate_128bit_trace_id=False,
+        baggage_header_prefix=constants.BAGGAGE_HEADER_PREFIX,
+        debug_id_header=constants.DEBUG_ID_HEADER_KEY,
+        one_span_per_rpc=False, extra_codecs=None,
+        tags=None,
+        max_tag_value_length=constants.MAX_TAG_VALUE_LENGTH,
+        throttler=None,
+        scope_manager=None,
+    ):
+        self.service_name = service_name
+        self.reporter = reporter
+        self.sampler = sampler
+        self.metrics_factory = metrics_factory or LegacyMetricsFactory(metrics or Metrics())
+        self.metrics = TracerMetrics(self.metrics_factory)
+        self.random = random.Random(time.time() * (os.getpid() or 1))
+        self.debug_id_header = debug_id_header
+        self.one_span_per_rpc = one_span_per_rpc
+        self.max_tag_value_length = max_tag_value_length
+        self.max_trace_id_bits = constants._max_trace_id_bits if generate_128bit_trace_id \
+            else constants._max_id_bits
+        self.codecs = {
+            Format.TEXT_MAP: TextCodec(
+                url_encoding=False,
+                trace_id_header=trace_id_header,
+                baggage_header_prefix=baggage_header_prefix,
+                debug_id_header=debug_id_header,
+            ),
+            Format.HTTP_HEADERS: TextCodec(
+                url_encoding=True,
+                trace_id_header=trace_id_header,
+                baggage_header_prefix=baggage_header_prefix,
+                debug_id_header=debug_id_header,
+            ),
+            Format.BINARY: BinaryCodec(),
+            ZipkinSpanFormat: ZipkinCodec(),
+        }
+        if extra_codecs:
+            self.codecs.update(extra_codecs)
+        self.tags = {
+            constants.JAEGER_VERSION_TAG_KEY: constants.JAEGER_CLIENT_VERSION,
+        }
+        if tags:
+            self.tags.update(tags)
+
+        if self.tags.get(constants.JAEGER_IP_TAG_KEY) is None:
+            self.tags[constants.JAEGER_IP_TAG_KEY] = local_ip()
+
+        if self.tags.get(constants.JAEGER_HOSTNAME_TAG_KEY) is None:
+            try:
+                hostname = socket.gethostname()
+                self.tags[constants.JAEGER_HOSTNAME_TAG_KEY] = hostname
+            except socket.error:
+                logger.exception('Unable to determine host name')
+
+        self.throttler = throttler
+        if self.throttler:
+            client_id = random.randint(0, sys.maxsize)
+            self.throttler._set_client_id(client_id)
+            self.tags[constants.CLIENT_UUID_TAG_KEY] = client_id
+
+        self.reporter.set_process(
+            service_name=self.service_name,
+            tags=self.tags,
+            max_length=self.max_tag_value_length,
+        )
+
+        super(Tracer, self).__init__(
+            scope_manager=scope_manager or ThreadLocalScopeManager()
+        )
+
+    def start_span(self,
+                   operation_name=None,
+                   child_of=None,
+                   references=None,
+                   tags=None,
+                   start_time=None,
+                   ignore_active_span=False,
+                   ):
+        """
+        Start and return a new Span representing a unit of work.
+
+        :param operation_name: name of the operation represented by the new
+            span from the perspective of the current service.
+        :param child_of: shortcut for 'child_of' reference
+        :param references: (optional) either a single Reference object or a
+            list of Reference objects that identify one or more parent
+            SpanContexts. (See the opentracing.Reference documentation for detail)
+        :param tags: optional dictionary of Span Tags. The caller gives up
+            ownership of that dictionary, because the Tracer may use it as-is
+            to avoid extra data copying.
+        :param start_time: an explicit Span start time as a unix timestamp per
+            time.time()
+        :param ignore_active_span: an explicit flag that ignores the current
+            active :class:`Scope` and creates a root :class:`Span`
+
+        :return: Returns an already-started Span instance.
+        """
+        parent = child_of
+
+        if self.active_span is not None \
+                and not ignore_active_span \
+                and not parent:
+            parent = self.active_span
+
+        # allow Span to be passed as reference, not just SpanContext
+        if isinstance(parent, Span):
+            parent = parent.context
+
+        valid_references = None
+        if references:
+            valid_references = list()
+            if not isinstance(references, list):
+                references = [references]
+            for reference in references:
+                if reference.referenced_context is not None:
+                    valid_references.append(reference)
+
+        # setting first reference as parent
+        if valid_references and (parent is None or not parent.has_trace):
+            parent = valid_references[0].referenced_context
+
+        rpc_server = tags and \
+            tags.get(ext_tags.SPAN_KIND) == ext_tags.SPAN_KIND_RPC_SERVER
+
+        if parent is None or not parent.has_trace:
+            trace_id = self._random_id(self.max_trace_id_bits)
+            span_id = self._random_id(constants._max_id_bits)
+            parent_id = None
+            flags = 0
+            baggage = None
+            if parent is None:
+                sampled, sampler_tags = \
+                    self.sampler.is_sampled(trace_id, operation_name)
+                if sampled:
+                    flags = SAMPLED_FLAG
+                    tags = tags or {}
+                    for k, v in six.iteritems(sampler_tags):
+                        tags[k] = v
+            elif parent.debug_id and self.is_debug_allowed(operation_name):
+                flags = SAMPLED_FLAG | DEBUG_FLAG
+                tags = tags or {}
+                tags[self.debug_id_header] = parent.debug_id
+            if parent and parent.baggage:
+                baggage = dict(parent.baggage)  # TODO do we need to clone?
+        else:
+            trace_id = parent.trace_id
+            if rpc_server and self.one_span_per_rpc:
+                # Zipkin-style one-span-per-RPC
+                span_id = parent.span_id
+                parent_id = parent.parent_id
+            else:
+                span_id = self._random_id(constants._max_id_bits)
+                parent_id = parent.span_id
+            flags = parent.flags
+            baggage = dict(parent.baggage)  # TODO do we need to clone?
+
+        span_ctx = SpanContext(trace_id=trace_id, span_id=span_id,
+                               parent_id=parent_id, flags=flags,
+                               baggage=baggage)
+        span = Span(context=span_ctx, tracer=self,
+                    operation_name=operation_name,
+                    tags=tags, start_time=start_time, references=valid_references)
+
+        self._emit_span_metrics(span=span, join=rpc_server)
+
+        return span
+
+    def start_active_span(self,
+                          operation_name=None,
+                          child_of=None,
+                          references=None,
+                          tags=None,
+                          start_time=None,
+                          ignore_active_span=False,
+                          finish_on_close=True,
+                          ):
+        """
+        Returns a newly started and activated :class:`Scope`
+
+        :param operation_name: name of the operation represented by the new
+            span from the perspective of the current service.
+        :param child_of: shortcut for 'child_of' reference
+        :param references: (optional) either a single Reference object or a
+            list of Reference objects that identify one or more parent
+            SpanContexts. (See the Reference documentation for detail)
+        :param tags: optional dictionary of Span Tags. The caller gives up
+            ownership of that dictionary, because the Tracer may use it as-is
+            to avoid extra data copying.
+        :param start_time: an explicit Span start time as a unix timestamp per
+            time.time()
+        :param ignore_active_span: an explicit flag that ignores the current
+            active :class:`Scope` and creates a root :class:`Span`
+        :param finish_on_close: whether :class:`Span` should automatically be
+            finished when :meth:`Scope.close()` is called.
+
+        :return: a :class:`Scope`, already registered via the :class:`ScopeManager`.
+        """
+        span = self.start_span(
+            operation_name=operation_name,
+            child_of=child_of,
+            references=references,
+            tags=tags,
+            start_time=start_time,
+            ignore_active_span=ignore_active_span,
+        )
+        return self.scope_manager.activate(span, finish_on_close)
+
+    def inject(self, span_context, format, carrier):
+        codec = self.codecs.get(format, None)
+        if codec is None:
+            raise UnsupportedFormatException(format)
+        if isinstance(span_context, Span):
+            # be flexible and allow Span as argument, not only SpanContext
+            span_context = span_context.context
+        if not isinstance(span_context, SpanContext):
+            raise ValueError(
+                'Expecting Jaeger SpanContext, not %s', type(span_context))
+        codec.inject(span_context=span_context, carrier=carrier)
+
+    def extract(self, format, carrier):
+        codec = self.codecs.get(format, None)
+        if codec is None:
+            raise UnsupportedFormatException(format)
+        return codec.extract(carrier)
+
+    def close(self):
+        """
+        Perform a clean shutdown of the tracer, flushing any traces that
+        may be buffered in memory.
+
+        :return: Returns a tornado.concurrent.Future that indicates if the
+            flush has been completed.
+        """
+        self.sampler.close()
+        return self.reporter.close()
+
+    def _emit_span_metrics(self, span, join=False):
+        if span.is_sampled():
+            self.metrics.spans_started_sampled(1)
+        else:
+            self.metrics.spans_started_not_sampled(1)
+        if not span.context.parent_id:
+            if span.is_sampled():
+                if join:
+                    self.metrics.traces_joined_sampled(1)
+                else:
+                    self.metrics.traces_started_sampled(1)
+            else:
+                if join:
+                    self.metrics.traces_joined_not_sampled(1)
+                else:
+                    self.metrics.traces_started_not_sampled(1)
+        return span
+
+    def report_span(self, span):
+        self.reporter.report_span(span)
+        self.metrics.spans_finished(1)
+
+    def random_id(self):
+        """
+        DEPRECATED: use _random_id() instead
+        """
+        return self.random.getrandbits(constants.MAX_ID_BITS)
+
+    def _random_id(self, bitsize):
+        return self.random.getrandbits(bitsize)
+
+    def is_debug_allowed(self, *args, **kwargs):
+        if not self.throttler:
+            return True
+        return self.throttler.is_allowed(*args, **kwargs)
+
+
+class TracerMetrics(object):
+    """Tracer specific metrics."""
+
+    def __init__(self, metrics_factory):
+        self.traces_started_sampled = \
+            metrics_factory.create_counter(name='jaeger:traces',
+                                           tags={'state': 'started', 'sampled': 'y'})
+        self.traces_started_not_sampled = \
+            metrics_factory.create_counter(name='jaeger:traces',
+                                           tags={'state': 'started', 'sampled': 'n'})
+        self.traces_joined_sampled = \
+            metrics_factory.create_counter(name='jaeger:traces',
+                                           tags={'state': 'joined', 'sampled': 'y'})
+        self.traces_joined_not_sampled = \
+            metrics_factory.create_counter(name='jaeger:traces',
+                                           tags={'state': 'joined', 'sampled': 'n'})
+        self.spans_started_sampled = \
+            metrics_factory.create_counter(name='jaeger:started_spans', tags={'sampled': 'y'})
+        self.spans_started_not_sampled = \
+            metrics_factory.create_counter(name='jaeger:started_spans', tags={'sampled': 'n'})
+        self.spans_finished = \
+            metrics_factory.create_counter(name='jaeger:finished_spans')

+ 117 - 0
desktop/core/ext-py/jaeger-client-4.0.0/jaeger_client/utils.py

@@ -0,0 +1,117 @@
+# Copyright (c) 2016 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import socket
+import struct
+
+import time
+from six.moves import range
+
+
+class ErrorReporter(object):
+    """
+    Reports errors by emitting metrics, and if logger is provided,
+    logging the error message once every log_interval_minutes
+
+    N.B. metrics will be deprecated in the future
+    """
+
+    def __init__(self, metrics, logger=None, log_interval_minutes=15):
+        self.logger = logger
+        self.log_interval_minutes = log_interval_minutes
+        self._last_error_reported_at = time.time()
+
+    def error(self, *args):
+        if self.logger is None:
+            return
+
+        next_logging_deadline = \
+            self._last_error_reported_at + (self.log_interval_minutes * 60)
+        current_time = time.time()
+        if next_logging_deadline >= current_time:
+            # If we aren't yet at the next logging deadline
+            return
+
+        self.logger.error(*args)
+        self._last_error_reported_at = current_time
+
+
+def get_boolean(string, default):
+    string = str(string).lower()
+    if string in ['false', '0', 'none']:
+        return False
+    elif string in ['true', '1']:
+        return True
+    else:
+        return default
+
+
+def local_ip():
+    """Get the local network IP of this machine"""
+    try:
+        ip = socket.gethostbyname(socket.gethostname())
+    except IOError:
+        ip = socket.gethostbyname('localhost')
+    if ip.startswith('127.'):
+        ip = get_local_ip_by_interfaces()
+        if ip is None:
+            ip = get_local_ip_by_socket()
+    return ip
+
+
+def get_local_ip_by_socket():
+    # Explanation : https://stackoverflow.com/questions/166506
+    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+    try:
+        # doesn't even have to be reachable
+        s.connect(('10.255.255.255', 1))
+        ip = s.getsockname()[0]
+    except IOError:
+        ip = None
+    finally:
+        s.close()
+    return ip
+
+
+def get_local_ip_by_interfaces():
+    ip = None
+    # Check eth0, eth1, eth2, en0, ...
+    interfaces = [
+        i + bytes(n) for i in (b'eth', b'en', b'wlan') for n in range(3)
+    ]  # :(
+    for interface in interfaces:
+        try:
+            ip = interface_ip(interface)
+            if ip is not None:
+                break
+        except IOError:
+            pass
+    return ip
+
+
+def interface_ip(interface):
+    try:
+        import fcntl
+        """Determine the IP assigned to us by the given network interface."""
+        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+        return socket.inet_ntoa(
+            fcntl.ioctl(
+                sock.fileno(), 0x8915, struct.pack('256s', interface[:15])
+            )[20:24]
+        )
+    except ImportError:
+        return None
+    # Explanation:
+    # http://stackoverflow.com/questions/11735821/python-get-localhost-ip
+    # http://stackoverflow.com/questions/24196932/how-can-i-get-the-ip-address-of-eth0-in-python

+ 1 - 0
desktop/core/ext-py/jaeger-client-4.0.0/requirements-dev.txt

@@ -0,0 +1 @@
+zest.releaser

+ 5 - 0
desktop/core/ext-py/jaeger-client-4.0.0/requirements-tests.txt

@@ -0,0 +1,5 @@
+# add dependencies in setup.py
+
+-r requirements.txt
+
+-e .[tests]

+ 3 - 0
desktop/core/ext-py/jaeger-client-4.0.0/requirements.txt

@@ -0,0 +1,3 @@
+# add dependencies in setup.py
+
+-e .

+ 28 - 0
desktop/core/ext-py/jaeger-client-4.0.0/setup.cfg

@@ -0,0 +1,28 @@
+[flake8]
+ignore = D100,D101,D102,D103,D104,D105,D203,D204,D205,D400
+max-line-length = 100
+exclude = 
+	jaeger_client/thrift_gen/*,
+	crossdock/thrift_gen/*
+
+[zest.releaser]
+release = no
+history_file = CHANGELOG.rst
+python-file-with-version = jaeger_client/__init__.py
+
+[tool:pytest]
+timeout = 60
+norecursedirs = 
+	.git
+	.tox
+	dist
+	build
+python_files = 
+	test_*.py
+	*_test.py
+	tests.py
+
+[egg_info]
+tag_build = 
+tag_date = 0
+

+ 74 - 0
desktop/core/ext-py/jaeger-client-4.0.0/setup.py

@@ -0,0 +1,74 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+import re
+
+from setuptools import setup, find_packages
+
+version = None
+with open('jaeger_client/__init__.py', 'r') as f:
+    for line in f:
+        m = re.match(r'^__version__\s*=\s*(["\'])([^"\']+)\1', line)
+        if m:
+            version = m.group(2)
+            break
+
+assert version is not None, \
+    'Could not determine version number from jaeger_client/__init__.py'
+
+setup(
+    name='jaeger-client',
+    version=version,
+    url='https://github.com/jaegertracing/jaeger-client-python',
+    description='Jaeger Python OpenTracing Tracer implementation',
+    author='Yuri Shkuro',
+    author_email='ys@uber.com',
+    packages=find_packages(exclude=['crossdock', 'tests', 'example', 'tests.*']),
+    include_package_data=True,
+    license='Apache License 2.0',
+    zip_safe=False,
+    keywords='jaeger, tracing, opentracing',
+    classifiers=[
+        'Development Status :: 5 - Production/Stable',
+        'Intended Audience :: Developers',
+        'License :: OSI Approved :: Apache Software License',
+        'Natural Language :: English',
+        'Programming Language :: Python :: 2.7',
+        'Programming Language :: Python :: 3.5',
+        'Programming Language :: Python :: 3.6',
+    ],
+    install_requires=[
+        'threadloop>=1,<2',
+        'thrift',
+        'tornado>=4.3,<5',
+        'opentracing>=2.1,<3.0',
+    ],
+    # Uncomment below if need to test with unreleased version of opentracing
+    # dependency_links=[
+    #     'git+ssh://git@github.com/opentracing/opentracing-python.git@BRANCHNAME#egg=opentracing',
+    # ],
+    test_suite='tests',
+    extras_require={
+        ':python_version<"3"': [
+            'futures',
+        ],
+        'tests': [
+            'mock==1.0.1',
+            'pycurl>=7.43,<8',
+            # pinned to avoid RemovedInPytest4Warning
+            'pytest>=3.7.0,<3.8.0',
+            'pytest-cov==2.5.1',
+            'coverage<4.4',  # can remove after https://bitbucket.org/ned/coveragepy/issues/581/44b1-44-breaking-in-ci
+            'pytest-timeout==1.3.1',
+            'pytest-tornado',
+            # pin <3.2 as otherwise it requires pytest>=3.8
+            'pytest-benchmark[histogram]>=3.0.0rc1,<3.2',
+            'pytest-localserver',
+            'flake8',
+            'flake8-quotes',
+            'codecov',
+            'tchannel>=0.27', # This is only used in python 2
+            'opentracing_instrumentation>=2,<3',
+            'prometheus_client==0.3.1',
+        ]
+    },
+)

+ 13 - 0
desktop/core/ext-py/jaeger-client-4.0.0/tests/__init__.py

@@ -0,0 +1,13 @@
+# Copyright (c) 2016 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.

+ 30 - 0
desktop/core/ext-py/jaeger-client-4.0.0/tests/conftest.py

@@ -0,0 +1,30 @@
+# Copyright (c) 2016 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import mock
+import pytest
+from jaeger_client import ConstSampler, Tracer
+from tornado.httpclient import AsyncHTTPClient
+
+
+@pytest.fixture(scope='function')
+def tracer():
+    reporter = mock.MagicMock()
+    sampler = ConstSampler(True)
+    return Tracer(
+        service_name='test_service_1', reporter=reporter, sampler=sampler)
+
+
+AsyncHTTPClient.configure('tornado.curl_httpclient.CurlAsyncHTTPClient')
+print('Configured AsyncHTTPClient to use tornado.curl_httpclient.CurlAsyncHTTPClient')

+ 47 - 0
desktop/core/ext-py/jaeger-client-4.0.0/tests/test_TUDPTransport.py

@@ -0,0 +1,47 @@
+# Copyright (c) 2016 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+import unittest
+
+from jaeger_client.TUDPTransport import TUDPTransport
+
+
+class TUDPTransportTests(unittest.TestCase):
+    def setUp(self):
+        self.t = TUDPTransport('127.0.0.1', 12345)
+
+    def test_constructor_blocking(self):
+        t = TUDPTransport('127.0.0.1', 12345, blocking=True)
+        assert t.transport_sock.gettimeout() is None
+
+    def test_constructor_nonblocking(self):
+        t = TUDPTransport('127.0.0.1', 12345, blocking=False)
+        assert t.transport_sock.gettimeout() == 0
+
+    def test_write(self):
+        self.t.write(b'hello')
+
+    def test_isopen_when_open(self):
+        assert self.t.isOpen() is True
+
+    def test_isopen_when_closed(self):
+        self.t.close()
+        assert self.t.isOpen() is False
+
+    def test_close(self):
+        self.t.close()
+        with self.assertRaises(Exception):
+            # Something bad should happen if we send on a closed socket..
+            self.t.write(b'hello')

+ 39 - 0
desktop/core/ext-py/jaeger-client-4.0.0/tests/test_api.py

@@ -0,0 +1,39 @@
+# Copyright (c) 2016 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+
+import unittest
+
+from jaeger_client import ConstSampler, Tracer
+from jaeger_client.reporter import NullReporter
+from opentracing.harness.api_check import APICompatibilityCheckMixin
+
+
+class APITest(unittest.TestCase, APICompatibilityCheckMixin):
+
+    reporter = NullReporter()
+    sampler = ConstSampler(True)
+    _tracer = Tracer(
+        service_name='test_service_1', reporter=reporter, sampler=sampler)
+
+    def tracer(self):
+        return APITest._tracer
+
+    def test_binary_propagation(self):
+        # TODO binary codecs are not implemented at the moment
+        pass
+
+    def is_parent(self, parent, span):
+        return span.parent_id == getattr(parent, 'span_id', None)

+ 566 - 0
desktop/core/ext-py/jaeger-client-4.0.0/tests/test_codecs.py

@@ -0,0 +1,566 @@
+# Copyright (c) 2016 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+
+import unittest
+from collections import namedtuple
+from itertools import product
+import six
+
+import mock
+import pytest
+from jaeger_client import Span, SpanContext, Tracer, ConstSampler
+from jaeger_client.codecs import (
+    Codec, TextCodec, BinaryCodec, ZipkinCodec, ZipkinSpanFormat, B3Codec,
+    span_context_from_string,
+    span_context_to_string,
+)
+from jaeger_client.config import Config
+from jaeger_client.reporter import InMemoryReporter
+from jaeger_client import constants
+from opentracing import Format
+from opentracing.propagation import (
+    InvalidCarrierException,
+    SpanContextCorruptedException,
+)
+
+
+class TestCodecs(unittest.TestCase):
+
+    def test_abstract_codec(self):
+        codec = Codec()
+        with self.assertRaises(NotImplementedError):
+            codec.inject({}, {})
+        with self.assertRaises(NotImplementedError):
+            codec.extract({})
+
+    def test_wrong_carrier(self):
+        codec = TextCodec()
+        with self.assertRaises(InvalidCarrierException):
+            codec.inject(span_context={}, carrier=[])  # array is no good
+        with self.assertRaises(InvalidCarrierException):
+            codec.extract(carrier=[])
+
+    def test_trace_context_from_bad_string(self):
+        tests = [
+            (123.321, 'not a string'),
+            ('bad value', 'bad string'),
+            ('1:1:1:1:1', 'Too many colons'),
+            ('1:1:1', 'Too few colons'),
+            ('x:1:1:1', 'Not all numbers'),
+            ('1:x:1:1', 'Not all numbers'),
+            ('1:1:x:1', 'Not all numbers'),
+            ('1:1:1:x', 'Not all numbers'),
+            ('0:1:1:1', 'Trace ID cannot be zero'),
+            ('1:0:1:1', 'Span ID cannot be zero'),
+            ('1:1:-1:1', 'Parent ID cannot be negative'),
+            ('1:1::1', 'Parent ID is missing'),
+            ('1:1:1:-1', 'Flags cannot be negative'),
+        ]
+
+        for test in tests:
+            try:
+                val = span_context_from_string(test[0])
+            except SpanContextCorruptedException:
+                val = None
+            self.assertEqual(val, None, test[1])
+
+    def test_trace_context_from_to_string(self):
+        to_string = span_context_to_string
+        from_string = span_context_from_string
+
+        tests = [
+            [(256, 127, None, 1), '100:7f:0:1'],
+            [(256, 127, 256, 0), '100:7f:100:0'],
+            [(0xffffffffffffffffffffffffffffffff, 127, 256, 0),
+             'ffffffffffffffffffffffffffffffff:7f:100:0'],
+        ]
+        for test in tests:
+            ctx = test[0]
+            value = to_string(*ctx)
+            self.assertEqual(value, test[1])
+            ctx_rev = from_string(value)
+            self.assertEqual(ctx_rev, ctx)
+
+        ctx_rev = from_string(['100:7f:100:0'])
+        assert ctx_rev == (256, 127, 256, 0), 'Array is acceptable'
+
+        with self.assertRaises(SpanContextCorruptedException):
+            from_string(['100:7f:100:0', 'garbage'])
+
+        ctx_rev = from_string(u'100:7f:100:0')
+        assert ctx_rev == (256, 127, 256, 0), 'Unicode is acceptable'
+
+    def test_context_to_readable_headers(self):
+        for url_encoding in [False, True]:
+            codec = TextCodec(
+                url_encoding=url_encoding,
+                trace_id_header='Trace_ID',
+                baggage_header_prefix='Trace-Attr-')
+            ctx = SpanContext(
+                trace_id=256, span_id=127, parent_id=None, flags=1
+            )
+            carrier = {}
+            codec.inject(ctx, carrier)
+            assert carrier == {'trace-id': '100:7f:0:1'}
+
+            ctx._baggage = {
+                'bender': 'Countess de la Roca',
+                'fry': u'Leela',
+                b'key1': bytes(chr(75)) if six.PY2 else bytes([75]),
+                u'key2': 'cafe',
+                u'key3': u'\U0001F47E',
+            }
+            carrier = {}
+            codec.inject(ctx, carrier)
+            # NB: the reverse transformation is not exact, e.g. this fails:
+            #   assert ctx._baggage == codec.extract(carrier)._baggage
+            # But fully supporting lossless Unicode baggage is not the goal.
+            if url_encoding:
+                assert carrier == {
+                    'trace-id': '100:7f:0:1',
+                    'trace-attr-bender': 'Countess%20de%20la%20Roca',
+                    'trace-attr-fry': 'Leela',
+                    'trace-attr-key1': 'K',
+                    'trace-attr-key2': 'cafe',
+                    'trace-attr-key3': '%F0%9F%91%BE',
+                }, 'with url_encoding = %s' % url_encoding
+                for key, val in six.iteritems(carrier):
+                    assert isinstance(key, str)
+                    assert isinstance(val, str), '%s' % type(val)
+            else:
+                assert carrier == {
+                    'trace-id': '100:7f:0:1',
+                    'trace-attr-bender': 'Countess de la Roca',
+                    'trace-attr-fry': 'Leela',
+                    'trace-attr-key1': 'K',
+                    u'trace-attr-key2': 'cafe',
+                    'trace-attr-key3': u'\U0001F47E',
+                }, 'with url_encoding = %s' % url_encoding
+
+    def test_context_from_bad_readable_headers(self):
+        codec = TextCodec(trace_id_header='Trace_ID',
+                          baggage_header_prefix='Trace-Attr-')
+
+        ctx = codec.extract(dict())
+        assert ctx is None, 'No headers'
+
+        bad_headers = {
+            '_Trace_ID': '100:7f:0:1',
+            '_trace-attr-Kiff': 'Amy'
+        }
+        ctx = codec.extract(bad_headers)
+        assert ctx is None, 'Bad header names'
+
+        with self.assertRaises(InvalidCarrierException):
+            codec.extract(carrier=[])  # not a dict
+
+        good_headers_bad_values = {
+            'Trace-ID': '100:7f:0:1xxx',
+            'trace-attr-Kiff': 'Amy'
+        }
+        with self.assertRaises(SpanContextCorruptedException):
+            codec.extract(good_headers_bad_values)
+
+    def test_context_from_readable_headers(self):
+        # provide headers all the way through Config object
+        config = Config(
+            service_name='test',
+            config={
+                'trace_id_header': 'Trace_ID',
+                'baggage_header_prefix': 'Trace-Attr-',
+            })
+        tracer = config.create_tracer(
+            reporter=InMemoryReporter(),
+            sampler=ConstSampler(True),
+        )
+        for url_encoding in [False, True]:
+            if url_encoding:
+                codec = tracer.codecs[Format.HTTP_HEADERS]
+                headers = {
+                    'Trace-ID': '100%3A7f:0:1',
+                    'trace-attr-Kiff': 'Amy%20Wang',
+                    'trace-atTR-HERMES': 'LaBarbara%20Hermes'
+                }
+            else:
+                codec = tracer.codecs[Format.HTTP_HEADERS]
+                headers = {
+                    'Trace-ID': '100:7f:0:1',
+                    'trace-attr-Kiff': 'Amy Wang',
+                    'trace-atTR-HERMES': 'LaBarbara Hermes'
+                }
+            ctx = codec.extract(headers)
+            assert ctx.trace_id == 256
+            assert ctx.span_id == 127
+            assert ctx.parent_id is None
+            assert ctx.flags == 1
+            assert ctx.baggage == {
+                'kiff': 'Amy Wang',
+                'hermes': 'LaBarbara Hermes',
+            }
+
+    def test_context_from_large_ids(self):
+        codec = TextCodec(trace_id_header='Trace_ID',
+                          baggage_header_prefix='Trace-Attr-')
+        headers = {
+            'Trace-ID': 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:FFFFFFFFFFFFFFFF:FFFFFFFFFFFFFFFF:1',
+        }
+        context = codec.extract(headers)
+        assert context.trace_id == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+        assert context.trace_id == (1 << 128) - 1
+        assert context.trace_id > 0
+        assert context.span_id == 0xFFFFFFFFFFFFFFFF
+        assert context.span_id == (1 << 64) - 1
+        assert context.span_id > 0
+        assert context.parent_id == 0xFFFFFFFFFFFFFFFF
+        assert context.parent_id == (1 << 64) - 1
+        assert context.parent_id > 0
+
+    def test_zipkin_codec_extract(self):
+        codec = ZipkinCodec()
+
+        t = namedtuple('Tracing', 'span_id parent_id trace_id traceflags')
+        carrier = t(span_id=1, parent_id=2, trace_id=3, traceflags=1)
+        context = codec.extract(carrier)
+        assert 3 == context.trace_id
+        assert 2 == context.parent_id
+        assert 1 == context.span_id
+        assert 1 == context.flags
+        assert context.baggage == {}
+
+        t = namedtuple('Tracing', 'something')
+        carrier = t(something=1)
+        with self.assertRaises(InvalidCarrierException):
+            codec.extract(carrier)
+
+        t = namedtuple('Tracing', 'trace_id')
+        carrier = t(trace_id=1)
+        with self.assertRaises(InvalidCarrierException):
+            codec.extract(carrier)
+
+        t = namedtuple('Tracing', 'trace_id span_id')
+        carrier = t(trace_id=1, span_id=1)
+        with self.assertRaises(InvalidCarrierException):
+            codec.extract(carrier)
+
+        t = namedtuple('Tracing', 'trace_id span_id parent_id')
+        carrier = t(trace_id=1, span_id=1, parent_id=1)
+        with self.assertRaises(InvalidCarrierException):
+            codec.extract(carrier)
+
+        carrier = {'span_id': 1, 'parent_id': 2, 'trace_id': 3,
+                   'traceflags': 1}
+        context = codec.extract(carrier)
+        assert 3 == context.trace_id
+        assert 2 == context.parent_id
+        assert 1 == context.span_id
+        assert 1 == context.flags
+        assert context.baggage == {}
+
+        carrier['trace_id'] = 0
+        assert codec.extract(carrier) is None
+
+    def test_zipkin_codec_inject(self):
+        codec = ZipkinCodec()
+
+        with self.assertRaises(InvalidCarrierException):
+            codec.inject(span_context=None, carrier=[])
+
+        ctx = SpanContext(trace_id=256, span_id=127, parent_id=None, flags=1)
+        span = Span(context=ctx, operation_name='x', tracer=None, start_time=1)
+        carrier = {}
+        codec.inject(span_context=span, carrier=carrier)
+        assert carrier == {'span_id': 127, 'parent_id': None,
+                           'trace_id': 256, 'traceflags': 1}
+
+    def test_zipkin_b3_codec_inject(self):
+        codec = B3Codec()
+
+        with self.assertRaises(InvalidCarrierException):
+            codec.inject(span_context=None, carrier=[])
+
+        ctx = SpanContext(trace_id=256, span_id=127, parent_id=None, flags=2)
+        span = Span(context=ctx, operation_name='x', tracer=None, start_time=1)
+        carrier = {}
+        codec.inject(span_context=span, carrier=carrier)
+        assert carrier == {'X-B3-SpanId': format(127, 'x').zfill(16),
+                           'X-B3-TraceId': format(256, 'x').zfill(16), 'X-B3-Flags': '1'}
+
+    def test_b3_codec_inject_parent(self):
+        codec = B3Codec()
+
+        with self.assertRaises(InvalidCarrierException):
+            codec.inject(span_context=None, carrier=[])
+
+        ctx = SpanContext(trace_id=256, span_id=127, parent_id=32, flags=1)
+        span = Span(context=ctx, operation_name='x', tracer=None, start_time=1)
+        carrier = {}
+        codec.inject(span_context=span, carrier=carrier)
+        assert carrier == {'X-B3-SpanId': format(127, 'x').zfill(16),
+                           'X-B3-ParentSpanId': format(32, 'x').zfill(16),
+                           'X-B3-TraceId': format(256, 'x').zfill(16), 'X-B3-Sampled': '1'}
+
+    def test_b3_extract(self):
+        codec = B3Codec()
+
+        with self.assertRaises(InvalidCarrierException):
+            codec.extract([])
+
+        # Implicit case insensitivity testing
+        carrier = {'X-b3-SpanId': 'a2fb4a1d1a96d312', 'X-B3-ParentSpanId': '0020000000000001',
+                   'X-B3-traceId': '463ac35c9f6413ad48485a3953bb6124', 'X-B3-flags': '1'}
+
+        span_context = codec.extract(carrier)
+        assert span_context.span_id == int('a2fb4a1d1a96d312', 16)
+        assert span_context.trace_id == int('463ac35c9f6413ad48485a3953bb6124', 16)
+        assert span_context.parent_id == int('0020000000000001', 16)
+        assert span_context.flags == 0x02
+
+        # validate that missing parentspanid does not cause an error
+        carrier.pop('X-B3-ParentSpanId')
+        span_context = codec.extract(carrier)
+        assert span_context.parent_id is None
+
+        carrier.update({'X-b3-sampled': '1'})
+
+        span_context = codec.extract(carrier)
+        assert span_context.flags == 0x03
+
+        carrier.pop('X-B3-flags')
+        span_context = codec.extract(carrier)
+        assert span_context.flags == 0x01
+
+        # validate present debug header with falsy value
+        carrier = {'X-b3-SpanId': 'a2fb4a1d1a96d312', 'X-B3-flags': '0',
+                   'X-B3-traceId': '463ac35c9f6413ad48485a3953bb6124'}
+        span_context = codec.extract(carrier)
+        assert span_context.flags == 0x00
+
+        # validate missing context
+        assert codec.extract({}) is None
+
+        # validate explicit none in context
+        carrier = {'X-b3-SpanId': None,
+                   'X-B3-traceId': '463ac35c9f6413ad48485a3953bb6124'}
+        assert codec.extract(carrier) is None
+
+        # validate invalid hex string
+        with self.assertRaises(SpanContextCorruptedException):
+            codec.extract({'x-B3-TraceId': 'a2fb4a1d1a96d312z'})
+
+        # validate non-string header
+        with self.assertRaises(SpanContextCorruptedException):
+            codec.extract({'x-B3-traceId': 123})
+
+    def test_zipkin_b3_codec_extract_injected(self):
+        codec = B3Codec()
+        ctx = SpanContext(trace_id=256, span_id=127, parent_id=None, flags=0)
+        span = Span(context=ctx, operation_name='x', tracer=None, start_time=1)
+        carrier = {}
+        codec.inject(span_context=span, carrier=carrier)
+
+        extracted = codec.extract(carrier)
+        assert extracted.trace_id == ctx.trace_id
+        assert extracted.span_id == ctx.span_id
+        assert extracted.parent_id == ctx.parent_id
+        assert extracted.flags == ctx.flags
+
+    def test_binary_codec(self):
+        codec = BinaryCodec()
+        with self.assertRaises(InvalidCarrierException):
+            codec.inject({}, {})
+        with self.assertRaises(InvalidCarrierException):
+            codec.extract({})
+
+
+def test_default_baggage_without_trace_id(tracer):
+    _test_baggage_without_trace_id(
+        tracer=tracer,
+        trace_id_header='Trace_ID',
+        baggage_header_prefix='Trace-baggage-',
+        headers={
+            'Trace-ID': '1:7f:0:1',
+            'trace-baggage-Kiff': 'Amy',
+            'trace-BAGGAGE-HERMES': 'LaBarbara',
+        },
+        match={
+            'kiff': 'Amy',
+            'hermes': 'LaBarbara',
+        },
+    )
+
+
+def test_ad_hoc_baggage_without_trace_id(tracer):
+    _test_baggage_without_trace_id(
+        tracer=tracer,
+        trace_id_header='Trace_ID',
+        baggage_header_prefix='Trace-baggage-',
+        headers={
+            'Trace-ID': '1:7f:0:1',
+            'jaeger-baggage': 'kiff=Amy, hermes=LaBarbara, bender=Bender',
+        },
+        match={
+            'kiff': 'Amy',
+            'hermes': 'LaBarbara',
+            'bender': 'Bender',
+        },
+    )
+
+
+def _test_baggage_without_trace_id(tracer, trace_id_header, baggage_header_prefix, headers, match):
+    codec = TextCodec(
+        trace_id_header=trace_id_header,
+        baggage_header_prefix=baggage_header_prefix,
+    )
+    with mock.patch('jaeger_client.codecs.span_context_from_string') as \
+            from_str:
+        from_str.return_value = (0, 1, 1, 1)  # make trace ID == 0 (i.e. invalid)
+        span_context = codec.extract(headers)
+        span = tracer.start_span('test', child_of=span_context)
+        assert span.context.baggage == match
+        # also check baggage through API
+        for k, v in six.iteritems(match):
+            assert span.get_baggage_item(k) == v
+
+
+@pytest.mark.parametrize('fmt,carrier', [
+    (Format.TEXT_MAP, {}),
+    (Format.HTTP_HEADERS, {}),
+    (ZipkinSpanFormat, {}),
+])
+def test_round_trip(tracer, fmt, carrier):
+    tracer_128bit = Tracer(
+        service_name='test',
+        reporter=InMemoryReporter(),
+        sampler=ConstSampler(True),
+        generate_128bit_trace_id=True)
+
+    for tracer1, tracer2 in product([tracer, tracer_128bit], repeat=2):
+        span = tracer1.start_span('test-%s' % fmt)
+        tracer1.inject(span, fmt, carrier)
+        context = tracer2.extract(fmt, carrier)
+        span2 = tracer2.start_span('test-%s' % fmt, child_of=context)
+        assert span.trace_id == span2.trace_id
+
+
+def _text_codec_to_trace_id_string(carrier):
+    return carrier[constants.TRACE_ID_HEADER].split(':')[0]
+
+
+def _zipkin_codec_to_trace_id_string(carrier):
+    return '{:x}'.format(carrier['trace_id'])
+
+
+@pytest.mark.parametrize('fmt,carrier,get_trace_id', [
+    (Format.TEXT_MAP, {}, _text_codec_to_trace_id_string),
+    (Format.HTTP_HEADERS, {}, _text_codec_to_trace_id_string),
+    (ZipkinSpanFormat, {}, _zipkin_codec_to_trace_id_string),
+])
+def test_inject_with_128bit_trace_id(tracer, fmt, carrier, get_trace_id):
+    tracer_128bit = Tracer(
+        service_name='test',
+        reporter=InMemoryReporter(),
+        sampler=ConstSampler(True),
+        generate_128bit_trace_id=True)
+
+    for tracer in [tracer, tracer_128bit]:
+        length = tracer.max_trace_id_bits / 4
+        trace_id = (1 << 64) - 1 if length == 16 else (1 << 128) - 1
+        ctx = SpanContext(trace_id=trace_id, span_id=127, parent_id=None,
+                          flags=1)
+        span = Span(ctx, operation_name='test-%s' % fmt, tracer=None, start_time=1)
+        tracer.inject(span, fmt, carrier)
+        assert len(get_trace_id(carrier)) == length
+
+        # test if the trace_id arrived on wire remains same even if
+        # the tracer is configured for 64bit ids or 128bit ids
+        ctx = SpanContext(trace_id=(1 << 128) - 1, span_id=127, parent_id=None,
+                          flags=0)
+        span = tracer.start_span('test-%s' % fmt, child_of=ctx)
+        carrier = dict()
+        tracer.inject(span, fmt, carrier)
+        assert len(get_trace_id(carrier)) == 32
+
+        ctx = SpanContext(trace_id=(1 << 64) - 1, span_id=127, parent_id=None,
+                          flags=0)
+        span = tracer.start_span('test-%s' % fmt, child_of=ctx)
+        carrier = dict()
+        tracer.inject(span, fmt, carrier)
+        assert len(get_trace_id(carrier)) == 16
+
+
+def test_debug_id():
+    debug_header = 'correlation-id'
+    tracer = Tracer(
+        service_name='test',
+        reporter=InMemoryReporter(),
+        sampler=ConstSampler(True),
+        debug_id_header=debug_header,
+    )
+    tracer.codecs[Format.TEXT_MAP] = TextCodec(
+        url_encoding=False,
+        debug_id_header=debug_header,
+    )
+    carrier = {debug_header: 'Coraline'}
+    context = tracer.extract(Format.TEXT_MAP, carrier)
+    assert context.is_debug_id_container_only
+    assert context.debug_id == 'Coraline'
+    span = tracer.start_span('test', child_of=context)
+    assert span.is_debug()
+    assert span.is_sampled()
+    tags = [t for t in span.tags if t.key == debug_header]
+    assert len(tags) == 1
+    assert tags[0].vStr == 'Coraline'
+
+
+def test_baggage_as_unicode_strings_with_httplib(httpserver):
+    if six.PY2:
+        import urllib2
+        urllib_under_test = urllib2
+    else:
+        import urllib.request
+        urllib_under_test = urllib.request
+
+    # httpserver is provided by pytest-localserver
+    httpserver.serve_content(content='Hello', code=200, headers=None)
+
+    tracer = Tracer(
+        service_name='test',
+        reporter=InMemoryReporter(),
+        # don't sample to avoid logging baggage to the span
+        sampler=ConstSampler(False),
+    )
+    tracer.codecs[Format.TEXT_MAP] = TextCodec(url_encoding=True)
+
+    baggage = [
+        (b'key1', b'value'),
+        (u'key2', b'value'),
+        ('key3', u'value'),
+        (b'key4', bytes(chr(255)) if six.PY2 else bytes([255])),
+        (u'key5', u'\U0001F47E')
+    ]
+    for b in baggage:
+        span = tracer.start_span('test')
+        span.set_baggage_item(b[0], b[1])
+
+        headers = {}
+        tracer.inject(span_context=span.context,
+                      format=Format.TEXT_MAP,
+                      carrier=headers)
+        # make sure httplib doesn't blow up
+        request = urllib_under_test.Request(httpserver.url, None, headers)
+        response = urllib_under_test.urlopen(request)
+        assert response.read() == b'Hello'
+        response.close()

+ 175 - 0
desktop/core/ext-py/jaeger-client-4.0.0/tests/test_config.py

@@ -0,0 +1,175 @@
+# Copyright (c) 2016-2018 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+import os
+import unittest
+import opentracing.tracer
+from jaeger_client import Config, ConstSampler, ProbabilisticSampler, RateLimitingSampler
+from jaeger_client.config import DEFAULT_THROTTLER_PORT
+from jaeger_client.metrics import MetricsFactory
+from jaeger_client.reporter import NullReporter
+from jaeger_client import constants
+
+
+class ConfigTests(unittest.TestCase):
+
+    def test_enabled(self):
+        c = Config({'enabled': True}, service_name='x')
+        assert c.enabled
+        c = Config({'enabled': False}, service_name='x')
+        assert not c.enabled
+
+    def test_reporter_batch_size(self):
+        c = Config({'reporter_batch_size': 12345}, service_name='x')
+        assert c.reporter_batch_size == 12345
+        c = Config({}, service_name='x')
+        assert c.reporter_batch_size == 10
+
+    def test_tags(self):
+        os.environ['JAEGER_TAGS'] = 'a=b,c=d'
+        c = Config({'tags': {'e': 'f'}}, service_name='x')
+        assert c.tags == {'a': 'b', 'c': 'd', 'e': 'f'}
+        c.create_tracer(NullReporter(), ConstSampler(True))
+
+    def test_no_sampler(self):
+        c = Config({}, service_name='x')
+        assert c.sampler is None
+
+    def test_const_sampler(self):
+        c = Config({'sampler': {'type': 'const', 'param': True}},
+                   service_name='x')
+        assert type(c.sampler) is ConstSampler
+        assert c.sampler.decision
+        c = Config({'sampler': {'type': 'const', 'param': False}},
+                   service_name='x')
+        assert type(c.sampler) is ConstSampler
+        assert not c.sampler.decision
+
+    def test_probabilistic_sampler(self):
+        with self.assertRaises(Exception):
+            cfg = {'sampler': {'type': 'probabilistic', 'param': 'xx'}}
+            Config(cfg, service_name='x').sampler
+        c = Config({'sampler': {'type': 'probabilistic', 'param': 0.5}},
+                   service_name='x')
+        assert type(c.sampler) is ProbabilisticSampler
+        assert c.sampler.rate == 0.5
+
+    def test_rate_limiting_sampler(self):
+        with self.assertRaises(Exception):
+            cfg = {'sampler': {'type': 'rate_limiting', 'param': 'xx'}}
+            Config(cfg, service_name='x').sampler
+        c = Config({'sampler': {'type': 'rate_limiting', 'param': 1234}},
+                   service_name='x')
+        assert type(c.sampler) is RateLimitingSampler
+        assert c.sampler.traces_per_second == 1234
+
+    def test_bad_sampler(self):
+        c = Config({'sampler': {'type': 'bad-sampler'}}, service_name='x')
+        with self.assertRaises(ValueError):
+            c.sampler.is_sampled(0)
+
+    def test_agent_reporting_host(self):
+        c = Config({}, service_name='x')
+        assert c.local_agent_reporting_host == 'localhost'
+
+        c = Config({'local_agent': {'reporting_host': 'jaeger.local'}}, service_name='x')
+        assert c.local_agent_reporting_host == 'jaeger.local'
+
+        os.environ['JAEGER_AGENT_HOST'] = 'jaeger-env.local'
+        c = Config({}, service_name='x')
+        assert c.local_agent_reporting_host == 'jaeger-env.local'
+
+    def test_max_tag_value_length(self):
+        c = Config({}, service_name='x')
+        assert c.max_tag_value_length == constants.MAX_TAG_VALUE_LENGTH
+
+        c = Config({'max_tag_value_length': 333}, service_name='x')
+        assert c.max_tag_value_length == 333
+
+        t = c.create_tracer(NullReporter(), ConstSampler(True))
+        assert t.max_tag_value_length == 333
+
+    def test_propagation(self):
+        c = Config({}, service_name='x')
+        assert c.propagation == {}
+
+        c = Config({'propagation': 'b3'}, service_name='x')
+        assert len(c.propagation) == 1
+
+    def test_throttler(self):
+        c = Config({
+            'throttler': {}
+        }, service_name='x')
+        assert not c.throttler_group()
+        assert c.throttler_port == DEFAULT_THROTTLER_PORT
+        assert c.throttler_refresh_interval == constants.DEFAULT_THROTTLER_REFRESH_INTERVAL
+
+        c = Config({
+            'throttler': {
+                'port': '1234',
+                'refresh_interval': '10'
+            }
+        }, service_name='x')
+        assert c.throttler_group()
+        assert c.throttler_port == 1234
+        assert c.throttler_refresh_interval == 10
+
+        c = Config({}, service_name='x')
+        assert c.throttler_group() is None
+        assert c.throttler_port is None
+        assert c.throttler_refresh_interval is None
+
+    def test_for_unexpected_config_entries(self):
+        with self.assertRaises(Exception):
+            Config({'unexpected': 'value'}, validate=True)
+
+    def test_reporter_queue_size_valid(self):
+        config = Config({'reporter_queue_size': 100}, service_name='x', validate=True)
+        assert config.reporter_queue_size == 100
+
+    def test_missing_service_name(self):
+        with self.assertRaises(ValueError):
+            Config({})
+
+    def test_disable_metrics(self):
+        config = Config({'metrics': False}, service_name='x')
+        assert isinstance(config._metrics_factory, MetricsFactory)
+
+    def test_initialize_tracer(self):
+        c = Config({}, service_name='x')
+        tracer = c.initialize_tracer()
+
+        assert opentracing.tracer == tracer
+
+    def test_default_local_agent_reporting_port(self):
+        c = Config({}, service_name='x')
+        assert c.local_agent_reporting_port == 6831
+
+    def test_generate_128bit_trace_id(self):
+        c = Config({}, service_name='x')
+        assert c.generate_128bit_trace_id is False
+
+        c = Config({'generate_128bit_trace_id': True}, service_name='x')
+        assert c.generate_128bit_trace_id is True
+
+        os.environ['JAEGER_TRACEID_128BIT'] = 'true'
+        c = Config({'generate_128bit_trace_id': False}, service_name='x')
+        assert c.generate_128bit_trace_id is False
+
+        c = Config({}, service_name='x')
+        assert c.generate_128bit_trace_id is True
+
+        os.environ.pop('JAEGER_TRACEID_128BIT')
+        assert os.getenv('JAEGER_TRACEID_128BIT', None) is None

+ 171 - 0
desktop/core/ext-py/jaeger-client-4.0.0/tests/test_crossdock.py

@@ -0,0 +1,171 @@
+# Copyright (c) 2016-2018 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+
+import six
+import mock
+import json
+import os
+import pytest
+import opentracing
+from mock import MagicMock
+from tornado.httpclient import HTTPRequest
+from jaeger_client import Tracer, ConstSampler
+from jaeger_client.reporter import InMemoryReporter
+from crossdock.server.endtoend import EndToEndHandler, _determine_host_port, _parse_host_port
+
+if six.PY2:
+    from crossdock.server import server
+
+tchannel_port = '9999'
+
+
+@pytest.fixture
+def app():
+    """Required by pytest-tornado's http_server fixture"""
+    s = server.Server(int(tchannel_port))
+    s.tchannel.listen()
+    return server.make_app(s, EndToEndHandler())
+
+
+# noinspection PyShadowingNames
+@pytest.yield_fixture
+def tracer():
+    tracer = Tracer(
+        service_name='test-tracer',
+        sampler=ConstSampler(True),
+        reporter=InMemoryReporter(),
+    )
+    try:
+        yield tracer
+    finally:
+        tracer.close()
+
+
+PERMUTATIONS = []
+for s2 in ['HTTP', 'TCHANNEL']:
+    for s3 in ['HTTP', 'TCHANNEL']:
+        for sampled in [True, False]:
+            PERMUTATIONS.append((s2, s3, sampled))
+
+
+# noinspection PyShadowingNames
+@pytest.mark.parametrize('s2_transport,s3_transport,sampled', PERMUTATIONS)
+@pytest.mark.gen_test
+@pytest.mark.skipif(six.PY3, reason='crossdock tests need tchannel that only works with Python 2.7')
+def test_trace_propagation(
+        s2_transport, s3_transport, sampled, tracer,
+        base_url, http_port, http_client):
+
+    # verify that server is ready
+    yield http_client.fetch(
+        request=HTTPRequest(
+            url=base_url,
+            method='HEAD',
+        )
+    )
+
+    level3 = dict()
+    level3['serviceName'] = 'python'
+    level3['serverRole'] = 's3'
+    level3['transport'] = s3_transport
+    level3['host'] = 'localhost'
+    level3['port'] = str(http_port) if s3_transport == 'HTTP' else tchannel_port
+
+    level2 = dict()
+    level2['serviceName'] = 'python'
+    level2['serverRole'] = 's2'
+    level2['transport'] = s2_transport
+    level2['host'] = 'localhost'
+    level2['port'] = str(http_port) if s2_transport == 'HTTP' else tchannel_port
+    level2['downstream'] = level3
+
+    level1 = dict()
+    level1['baggage'] = 'Zoidberg'
+    level1['serverRole'] = 's1'
+    level1['sampled'] = sampled
+    level1['downstream'] = level2
+    body = json.dumps(level1)
+
+    with mock.patch('opentracing.tracer', tracer):
+        assert opentracing.tracer == tracer  # sanity check that patch worked
+
+        req = HTTPRequest(url='%s/start_trace' % base_url, method='POST',
+                          headers={'Content-Type': 'application/json'},
+                          body=body,
+                          request_timeout=2)
+
+        response = yield http_client.fetch(req)
+        assert response.code == 200
+        tr = server.serializer.traceresponse_from_json(response.body)
+        assert tr is not None
+        assert tr.span is not None
+        assert tr.span.baggage == level1.get('baggage')
+        assert tr.span.sampled == sampled
+        assert tr.span.traceId is not None
+        assert tr.downstream is not None
+        assert tr.downstream.span.baggage == level1.get('baggage')
+        assert tr.downstream.span.sampled == sampled
+        assert tr.downstream.span.traceId == tr.span.traceId
+        assert tr.downstream.downstream is not None
+        assert tr.downstream.downstream.span.baggage == level1.get('baggage')
+        assert tr.downstream.downstream.span.sampled == sampled
+        assert tr.downstream.downstream.span.traceId == tr.span.traceId
+
+
+# noinspection PyShadowingNames
+@pytest.mark.gen_test
+@pytest.mark.skipif(six.PY3, reason='crossdock tests need tchannel that only works with Python 2.7')
+def test_endtoend_handler(tracer):
+    payload = dict()
+    payload['operation'] = 'Zoidberg'
+    payload['count'] = 2
+    payload['tags'] = {'key': 'value'}
+    body = json.dumps(payload)
+
+    h = EndToEndHandler()
+    request = MagicMock(body=body)
+    response_writer = MagicMock()
+    response_writer.finish.return_value = None
+
+    h.tracers = {'remote': tracer}
+    h.generate_traces(request, response_writer)
+
+    spans = tracer.reporter.get_spans()
+    assert len(spans) == 2
+
+
+def test_determine_host_port():
+    original_value = os.environ.get('AGENT_HOST_PORT', None)
+    os.environ['AGENT_HOST_PORT'] = 'localhost:1234'
+    host, port = _determine_host_port()
+
+    # Before any assertions, restore original environment variable.
+    if original_value:
+        os.environ['AGENT_HOST_PORT'] = original_value
+
+    assert host == 'localhost'
+    assert port == 1234
+
+
+def test_parse_host_port():
+    test_cases = [
+        [('', 'localhost', 5678), ('localhost', 5678)],
+        [('test:1234', 'localhost', 5678), ('test', 1234)],
+    ]
+    for test_case in test_cases:
+        args, result = test_case
+        host, port = _parse_host_port(*args)
+        assert (host, port) == result

+ 92 - 0
desktop/core/ext-py/jaeger-client-4.0.0/tests/test_local_agent_net.py

@@ -0,0 +1,92 @@
+# Copyright (c) 2016 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import pytest
+import tornado.web
+from urllib.parse import urlparse
+from jaeger_client.local_agent_net import LocalAgentSender
+from jaeger_client.config import DEFAULT_REPORTING_PORT
+
+test_strategy = """
+    {
+        "strategyType":0,
+        "probabilisticSampling":
+        {
+            "samplingRate":0.002
+        }
+    }
+"""
+
+test_credits = """
+    {
+        \"balances\": [
+            {
+                \"operation\": \"test-operation\",
+                \"balance\": 2.0
+            }
+        ]
+    }
+"""
+
+test_client_id = 12345678
+
+
+class AgentHandler(tornado.web.RequestHandler):
+    def get(self):
+        self.write(test_strategy)
+
+
+class CreditHandler(tornado.web.RequestHandler):
+    def get(self):
+        self.write(test_credits)
+
+
+application = tornado.web.Application([
+    (r'/sampling', AgentHandler),
+    (r'/credits', CreditHandler),
+])
+
+
+@pytest.fixture
+def app():
+    return application
+
+
+@pytest.mark.gen_test
+def test_request_sampling_strategy(http_client, base_url):
+    o = urlparse(base_url)
+    sender = LocalAgentSender(
+        host='localhost',
+        sampling_port=o.port,
+        reporting_port=DEFAULT_REPORTING_PORT
+    )
+    response = yield sender.request_sampling_strategy(service_name='svc', timeout=15)
+    assert response.body == test_strategy.encode('utf-8')
+
+
+@pytest.mark.gen_test
+def test_request_throttling_credits(http_client, base_url):
+    o = urlparse(base_url)
+    sender = LocalAgentSender(
+        host='localhost',
+        sampling_port=o.port,
+        reporting_port=DEFAULT_REPORTING_PORT,
+        throttling_port=o.port,
+    )
+    response = yield sender.request_throttling_credits(
+        service_name='svc',
+        client_id=test_client_id,
+        operations=['test-operation'],
+        timeout=15)
+    assert response.body == test_credits.encode('utf-8')

+ 99 - 0
desktop/core/ext-py/jaeger-client-4.0.0/tests/test_metrics.py

@@ -0,0 +1,99 @@
+# Copyright (c) 2016 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+
+import mock
+
+from jaeger_client.metrics import MetricsFactory, Metrics,\
+    LegacyMetricsFactory
+
+
+def test_metrics_factory_noop():
+    mf = MetricsFactory()
+    mf.create_counter('foo')(1)
+    mf.create_timer('foo')(1)
+    mf.create_gauge('foo')(1)
+
+
+def test_metrics_count_func_called():
+    m = mock.MagicMock()
+    metrics = Metrics(count=m)
+    metrics.count('foo', 1)
+    assert m.call_args == (('foo', 1),)
+
+
+def test_metrics_timing_func_called():
+    m = mock.MagicMock()
+    metrics = Metrics(timing=m)
+    metrics.timing('foo', 1)
+    assert m.call_args == (('foo', 1),)
+
+
+def test_metrics_gauge_func_called():
+    m = mock.MagicMock()
+    metrics = Metrics(gauge=m)
+    metrics.gauge('foo', 1)
+    assert m.call_args == (('foo', 1),)
+
+
+def test_metrics_count_func_noops_if_given_uncallable_count_found():
+    metrics = Metrics(count=123)
+    metrics.count('foo', 1)
+
+
+def test_metrics_timing_func_noops_if_given_uncallable_timing_found():
+    metrics = Metrics(timing=123)
+    metrics.timing('foo', 1)
+
+
+def test_metrics_gauge_func_noops_if_given_uncallable_gauge_found():
+    metrics = Metrics(gauge=123)
+    metrics.gauge('foo', 1)
+
+
+def test_legacy_metrics_factory():
+    cm = mock.MagicMock()
+    tm = mock.MagicMock()
+    gm = mock.MagicMock()
+    mf = LegacyMetricsFactory(Metrics(count=cm, timing=tm, gauge=gm))
+    counter = mf.create_counter(name='foo', tags={'k': 'v', 'a': 'counter'})
+    counter(1)
+    assert cm.call_args == (('foo.a_counter.k_v', 1),)
+
+    gauge = mf.create_gauge(name='bar', tags={'k': 'v', 'a': 'gauge'})
+    gauge(2)
+    assert gm.call_args == (('bar.a_gauge.k_v', 2),)
+
+    timing = mf.create_timer(name='rawr', tags={'k': 'v', 'a': 'timer'})
+    timing(3)
+    assert tm.call_args == (('rawr.a_timer.k_v', 0.003),)
+
+    mf = LegacyMetricsFactory(Metrics(timing=tm))
+    timing = mf.create_timer(name='wow')
+    timing(4)
+    assert tm.call_args == (('wow', 0.004),), \
+        'building a timer with no tags should work'
+
+
+def test_legacy_metrics_factory_noop():
+    mf = LegacyMetricsFactory(Metrics())
+    counter = mf.create_counter(name='foo', tags={'a': 'counter'})
+    counter(1)
+
+    gauge = mf.create_gauge(name='bar', tags={'a': 'gauge'})
+    gauge(2)
+
+    timing = mf.create_timer(name='rawr', tags={'a': 'timer'})
+    timing(3)

+ 60 - 0
desktop/core/ext-py/jaeger-client-4.0.0/tests/test_noop_tracer.py

@@ -0,0 +1,60 @@
+# Copyright (c) 2016 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+from __future__ import absolute_import
+
+import opentracing
+from opentracing import Tracer, Format
+
+
+def test_new_trace():
+    tracer = Tracer()
+
+    span = tracer.start_span(operation_name='test')
+    span.set_baggage_item('Fry', 'Leela')
+    span.set_tag('x', 'y')
+    span.log_event('z')
+
+    child = tracer.start_span(operation_name='child',
+                              references=opentracing.child_of(span.context))
+    child.log_event('w')
+    assert child.get_baggage_item('Fry') is None
+    carrier = {}
+    tracer.inject(
+        span_context=child.context,
+        format=Format.TEXT_MAP,
+        carrier=carrier)
+    assert carrier == dict()
+    child.finish()
+
+    span.finish()
+
+
+def test_join_trace():
+    tracer = Tracer()
+
+    span_ctx = tracer.extract(format=Format.TEXT_MAP, carrier={})
+    span = tracer.start_span(operation_name='test',
+                             references=opentracing.child_of(span_ctx))
+    span.set_tag('x', 'y')
+    span.set_baggage_item('a', 'b')
+    span.log_event('z')
+
+    child = tracer.start_span(operation_name='child',
+                              references=opentracing.child_of(span.context))
+    child.log_event('w')
+    child.finish()
+
+    span.finish()

+ 56 - 0
desktop/core/ext-py/jaeger-client-4.0.0/tests/test_prometheus.py

@@ -0,0 +1,56 @@
+# Copyright (c) 2018, The Jaeger Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from jaeger_client.metrics.prometheus \
+    import PrometheusMetricsFactory
+from prometheus_client import REGISTRY
+
+
+def test_prometheus_metrics_counter():
+    metrics = PrometheusMetricsFactory(namespace='test')
+    counter1 = metrics.create_counter(name='jaeger:test_counter',
+                                      tags={'result': 'ok'})
+    counter1(1)
+    counter2 = metrics.create_counter(name='jaeger:test_counter',
+                                      tags={'result': 'ok'})
+    counter2(1)
+    after = REGISTRY.get_sample_value('test_jaeger:test_counter',
+                                      {'result': 'ok'})
+    assert 2 == after
+
+
+def test_prometheus_metrics_counter_without_tags():
+    metrics = PrometheusMetricsFactory()
+    counter = metrics.create_counter(name='jaeger:test_counter_no_tags')
+    counter(1)
+    after = REGISTRY.get_sample_value('jaeger:test_counter_no_tags')
+    assert 1 == after
+
+
+def test_prometheus_metrics_guage():
+    metrics = PrometheusMetricsFactory(namespace='test')
+    gauge = metrics.create_gauge(name='jaeger:test_gauge',
+                                 tags={'result': 'ok'})
+    gauge(1)
+    after = REGISTRY.get_sample_value('test_jaeger:test_gauge',
+                                      {'result': 'ok'})
+    assert 1 == after
+
+
+def test_prometheus_metrics_gauge_without_tags():
+    metrics = PrometheusMetricsFactory()
+    gauge = metrics.create_gauge(name='jaeger:test_gauge_no_tags')
+    gauge(1)
+    after = REGISTRY.get_sample_value('jaeger:test_gauge_no_tags')
+    assert 1 == after

+ 85 - 0
desktop/core/ext-py/jaeger-client-4.0.0/tests/test_rate_limiter.py

@@ -0,0 +1,85 @@
+# Copyright (c) 2017 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from six.moves import range
+import time
+import mock
+
+from jaeger_client.rate_limiter import RateLimiter
+
+
+def test_rate_limiting_sampler():
+    rate_limiter = RateLimiter(2, 2)
+    assert rate_limiter.balance <= 2.0
+    rate_limiter.balance = 2.0
+    # stop time by overwriting timestamp() function to always return
+    # the same time
+    ts = time.time()
+    rate_limiter.last_tick = ts
+    with mock.patch('jaeger_client.rate_limiter.RateLimiter.timestamp') \
+            as mock_time:
+        mock_time.side_effect = lambda: ts  # always return same time
+        assert rate_limiter.timestamp() == ts
+        assert rate_limiter.check_credit(1), 'initial balance allows first item'
+        assert rate_limiter.check_credit(1), 'initial balance allows second item'
+        assert not rate_limiter.check_credit(1), 'initial balance exhausted'
+
+        # move time 250ms forward, not enough credits to pay for one sample
+        mock_time.side_effect = lambda: ts + 0.25
+        assert not rate_limiter.check_credit(1), 'not enough time passed for full item'
+
+        # move time 500ms forward, now enough credits to pay for one sample
+        mock_time.side_effect = lambda: ts + 0.5
+        assert rate_limiter.check_credit(1), 'enough time for new item'
+        assert not rate_limiter.check_credit(1), 'no more balance'
+
+        # move time 5s forward, enough to accumulate credits for 10 samples,
+        # but it should still be capped at 2
+        rate_limiter.last_tick = ts  # reset the timer
+        mock_time.side_effect = lambda: ts + 5
+        assert rate_limiter.check_credit(1), 'enough time for new item'
+        assert rate_limiter.check_credit(1), 'enough time for second new item'
+        for i in range(0, 8):
+            assert not rate_limiter.check_credit(1), 'but no further, since time is stopped'
+
+    # Test with rate limit of greater than 1 second
+    rate_limiter = RateLimiter(0.1, 1.0)
+    assert rate_limiter.balance <= 1.0
+    rate_limiter.balance = 1.0
+    ts = time.time()
+    rate_limiter.last_tick = ts
+    with mock.patch('jaeger_client.rate_limiter.RateLimiter.timestamp') \
+            as mock_time:
+        mock_time.side_effect = lambda: ts  # always return same time
+        assert rate_limiter.timestamp() == ts
+        assert rate_limiter.check_credit(1), 'initial balance allows first item'
+        assert not rate_limiter.check_credit(1), 'initial balance exhausted'
+
+        # move time 11s forward, enough credits to pay for one sample
+        mock_time.side_effect = lambda: ts + 11
+        assert rate_limiter.check_credit(1)
+
+    # Test update
+    rate_limiter = RateLimiter(3.0, 3.0)
+    assert rate_limiter.balance <= 3.0
+    rate_limiter.balance = 3.0
+    ts = time.time()
+    rate_limiter.last_tick = ts
+    with mock.patch('jaeger_client.rate_limiter.RateLimiter.timestamp') \
+            as mock_time:
+        mock_time.side_effect = lambda: ts  # always return same time
+        assert rate_limiter.timestamp() == ts
+        assert rate_limiter.check_credit(1)
+        rate_limiter.update(2.0, 2.0)
+        assert rate_limiter.balance == 4.0 / 3.0

+ 298 - 0
desktop/core/ext-py/jaeger-client-4.0.0/tests/test_reporter.py

@@ -0,0 +1,298 @@
+# Copyright (c) 2016 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import print_function
+from six.moves import range
+
+import logging
+import time
+import collections
+
+import mock
+import pytest
+import tornado.gen
+import jaeger_client.reporter
+
+from tornado.concurrent import Future
+from jaeger_client import Span, SpanContext
+from jaeger_client.metrics import LegacyMetricsFactory, Metrics
+from jaeger_client.utils import ErrorReporter
+from tornado.ioloop import IOLoop
+from tornado.testing import AsyncTestCase, gen_test
+from jaeger_client.reporter import Reporter
+from jaeger_client.ioloop_util import future_result
+
+
+def test_null_reporter():
+    reporter = jaeger_client.reporter.NullReporter()
+    reporter.report_span({})
+    f = reporter.close()
+    f.result()
+
+
+def test_in_memory_reporter():
+    reporter = jaeger_client.reporter.InMemoryReporter()
+    reporter.report_span({})
+    f = reporter.close()
+    f.result()
+    spans = reporter.get_spans()
+    assert [{}] == spans
+
+
+def test_logging_reporter():
+    log_mock = mock.MagicMock()
+    reporter = jaeger_client.reporter.LoggingReporter(logger=log_mock)
+    reporter.report_span({})
+    log_mock.info.assert_called_with('Reporting span %s', {})
+    reporter.close().result()
+
+
+def test_composite_reporter():
+    reporter = jaeger_client.reporter.CompositeReporter(
+        jaeger_client.reporter.NullReporter(),
+        jaeger_client.reporter.LoggingReporter())
+    with mock.patch('jaeger_client.reporter.NullReporter.set_process') \
+            as null_mock:
+        with mock.patch('jaeger_client.reporter.LoggingReporter.set_process') \
+                as log_mock:
+            reporter.set_process('x', {}, 123)
+            null_mock.assert_called_with('x', {}, 123)
+            log_mock.assert_called_with('x', {}, 123)
+    with mock.patch('jaeger_client.reporter.NullReporter.report_span') \
+            as null_mock:
+        with mock.patch('jaeger_client.reporter.LoggingReporter.report_span') \
+                as log_mock:
+            reporter.report_span({})
+            null_mock.assert_called_with({})
+            log_mock.assert_called_with({})
+    with mock.patch('jaeger_client.reporter.NullReporter.close') \
+            as null_mock:
+        with mock.patch('jaeger_client.reporter.LoggingReporter.close') \
+                as log_mock:
+
+            f1 = Future()
+            f2 = Future()
+            null_mock.return_value = f1
+            log_mock.return_value = f2
+            f = reporter.close()
+            null_mock.assert_called_once()
+            log_mock.assert_called_once()
+            assert not f.done()
+            f1.set_result(True)
+            f2.set_result(True)
+            assert f.done()
+
+
+class FakeSender(object):
+    """
+    Mock the _send() method of the reporter by capturing requests
+    and returning incomplete futures that can be completed from
+    inside the test.
+    """
+    def __init__(self):
+        self.requests = []
+        self.futures = []
+
+    def __call__(self, spans):
+        # print('ManualSender called', request)
+        self.requests.append(spans)
+        fut = Future()
+        self.futures.append(fut)
+        return fut
+
+
+class HardErrorReporter(object):
+    def error(self, name, count, *args):
+        raise ValueError(*args)
+
+
+FakeTrace = collections.namedtuple(
+    'FakeTracer', ['ip_address', 'service_name'])
+
+
+class FakeMetricsFactory(LegacyMetricsFactory):
+    def __init__(self):
+        super(FakeMetricsFactory, self).__init__(
+            Metrics(count=self._incr_count)
+        )
+        self.counters = {}
+
+    def _incr_count(self, key, value):
+        self.counters[key] = value + self.counters.get(key, 0)
+
+
+class ReporterTest(AsyncTestCase):
+    @pytest.yield_fixture
+    def thread_loop(self):
+        yield
+
+    @staticmethod
+    def _new_span(name):
+        tracer = FakeTrace(ip_address='127.0.0.1',
+                           service_name='reporter_test')
+        ctx = SpanContext(trace_id=1,
+                          span_id=1,
+                          parent_id=None,
+                          flags=1)
+        span = Span(context=ctx,
+                    tracer=tracer,
+                    operation_name=name)
+        span.start_time = time.time()
+        span.end_time = span.start_time + 0.001  # 1ms
+        return span
+
+    @staticmethod
+    def _new_reporter(batch_size, flush=None, queue_cap=100):
+        reporter = Reporter(channel=mock.MagicMock(),
+                            io_loop=IOLoop.current(),
+                            batch_size=batch_size,
+                            flush_interval=flush,
+                            metrics_factory=FakeMetricsFactory(),
+                            error_reporter=HardErrorReporter(),
+                            queue_capacity=queue_cap)
+        reporter.set_process('service', {}, max_length=0)
+        sender = FakeSender()
+        reporter._send = sender
+        return reporter, sender
+
+    @tornado.gen.coroutine
+    def _wait_for(self, fn):
+        """Wait until fn() returns truth, but not longer than 1 second."""
+        start = time.time()
+        for i in range(1000):
+            if fn():
+                return
+            yield tornado.gen.sleep(0.001)
+        print('waited for condition %f seconds' % (time.time() - start))
+
+    @gen_test
+    def test_submit_batch_size_1(self):
+        reporter, sender = self._new_reporter(batch_size=1)
+        reporter.report_span(self._new_span('1'))
+
+        yield self._wait_for(lambda: len(sender.futures) > 0)
+        assert 1 == len(sender.futures)
+
+        sender.futures[0].set_result(1)
+        yield reporter.close()
+        assert 1 == len(sender.futures)
+
+        # send after close
+        span_dropped_key = 'jaeger:reporter_spans.result_dropped'
+        assert span_dropped_key not in reporter.metrics_factory.counters
+        reporter.report_span(self._new_span('1'))
+        assert 1 == reporter.metrics_factory.counters[span_dropped_key]
+
+    @gen_test
+    def test_submit_failure(self):
+        reporter, sender = self._new_reporter(batch_size=1)
+        reporter.error_reporter = ErrorReporter(
+            metrics=Metrics(), logger=logging.getLogger())
+
+        reporter_failure_key = 'jaeger:reporter_spans.result_err'
+        assert reporter_failure_key not in reporter.metrics_factory.counters
+
+        # simulate exception in send
+        reporter._send = mock.MagicMock(side_effect=ValueError())
+        reporter.report_span(self._new_span('1'))
+
+        yield self._wait_for(
+            lambda: reporter_failure_key in reporter.metrics_factory.counters)
+        assert 1 == reporter.metrics_factory.counters.get(reporter_failure_key)
+
+        # silly test, for code coverage only
+        yield reporter._submit([])
+
+    @gen_test
+    def test_submit_queue_full_batch_size_1(self):
+        reporter, sender = self._new_reporter(batch_size=1, queue_cap=1)
+        reporter.report_span(self._new_span('1'))
+
+        yield self._wait_for(lambda: len(sender.futures) > 0)
+        assert 1 == len(sender.futures)
+        # the consumer is blocked on a future, so won't drain the queue
+        reporter.report_span(self._new_span('2'))
+        span_dropped_key = 'jaeger:reporter_spans.result_dropped'
+        assert span_dropped_key not in reporter.metrics_factory.counters
+        reporter.report_span(self._new_span('3'))
+        yield self._wait_for(
+            lambda: span_dropped_key in reporter.metrics_factory.counters
+        )
+        assert 1 == reporter.metrics_factory.counters.get(span_dropped_key)
+        # let it drain the queue
+        sender.futures[0].set_result(1)
+        yield self._wait_for(lambda: len(sender.futures) > 1)
+        assert 2 == len(sender.futures)
+
+        sender.futures[1].set_result(1)
+        yield reporter.close()
+
+    @gen_test
+    def test_submit_batch_size_2(self):
+        reporter, sender = self._new_reporter(batch_size=2, flush=0.01)
+        reporter.report_span(self._new_span('1'))
+        yield tornado.gen.sleep(0.001)
+        assert 0 == len(sender.futures)
+
+        reporter.report_span(self._new_span('2'))
+        yield self._wait_for(lambda: len(sender.futures) > 0)
+        assert 1 == len(sender.futures)
+        assert 2 == len(sender.requests[0].spans)
+        sender.futures[0].set_result(1)
+
+        # 3rd span will not be submitted right away, but after `flush` interval
+        reporter.report_span(self._new_span('3'))
+        yield tornado.gen.sleep(0.001)
+        assert 1 == len(sender.futures)
+        yield tornado.gen.sleep(0.001)
+        assert 1 == len(sender.futures)
+        yield tornado.gen.sleep(0.01)
+        assert 2 == len(sender.futures)
+        sender.futures[1].set_result(1)
+
+        yield reporter.close()
+
+    @gen_test
+    def test_close_drains_queue(self):
+        reporter, sender = self._new_reporter(batch_size=1, flush=0.050)
+        reporter.report_span(self._new_span('0'))
+
+        yield self._wait_for(lambda: len(sender.futures) > 0)
+        assert 1 == len(sender.futures)
+
+        # now that the consumer is blocked on the first future.
+        # let's reset Send to actually respond right away
+        # and flood the queue with messages
+        count = [0]
+
+        def send(_):
+            count[0] += 1
+            return future_result(True)
+
+        reporter._send = send
+        reporter.batch_size = 3
+        for i in range(10):
+            reporter.report_span(self._new_span('%s' % i))
+        assert reporter.queue.qsize() == 10, 'queued 10 spans'
+
+        # now unblock consumer
+        sender.futures[0].set_result(1)
+        yield self._wait_for(lambda: count[0] > 2)
+
+        assert count[0] == 3, '9 out of 10 spans submitted in 3 batches'
+        assert reporter.queue._unfinished_tasks == 1, 'one span still pending'
+
+        yield reporter.close()
+        assert reporter.queue.qsize() == 0, 'all spans drained'
+        assert count[0] == 4, 'last span submitted in one extrac batch'

+ 723 - 0
desktop/core/ext-py/jaeger-client-4.0.0/tests/test_sampler.py

@@ -0,0 +1,723 @@
+# Copyright (c) 2016-2018 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import division
+from six.moves import range
+import time
+import math
+import mock
+import pytest
+
+from jaeger_client.sampler import (
+    Sampler,
+    ConstSampler,
+    ProbabilisticSampler,
+    RateLimitingSampler,
+    RemoteControlledSampler,
+    GuaranteedThroughputProbabilisticSampler,
+    AdaptiveSampler,
+    DEFAULT_MAX_OPERATIONS,
+    DEFAULT_SAMPLING_PROBABILITY,
+    get_sampling_probability,
+    get_rate_limit,
+)
+
+MAX_INT = 1 << 63
+
+
+def get_tags(type, param):
+    return {
+        'sampler.type': type,
+        'sampler.param': param,
+    }
+
+
+def test_abstract_sampler_errors():
+    sampler = Sampler()
+    with pytest.raises(NotImplementedError):
+        sampler.is_sampled(trace_id=123)
+    with pytest.raises(NotImplementedError):
+        sampler.close()
+
+
+def test_probabilistic_sampler_errors():
+    with pytest.raises(AssertionError):
+        ProbabilisticSampler(-0.1)
+    with pytest.raises(AssertionError):
+        ProbabilisticSampler(1.1)
+
+
+def test_probabilistic_sampler():
+    sampler = ProbabilisticSampler(0.5)
+    assert MAX_INT == 0x8000000000000000
+    sampled, tags = sampler.is_sampled(MAX_INT - 10)
+    assert sampled
+    assert tags == get_tags('probabilistic', 0.5)
+    sampled, _ = sampler.is_sampled(MAX_INT + 10)
+    assert not sampled
+    sampler.close()
+    assert '%s' % sampler == 'ProbabilisticSampler(0.5)'
+
+
+def test_const_sampler():
+    sampler = ConstSampler(True)
+    sampled, _ = sampler.is_sampled(1)
+    assert sampled
+    sampled, _ = sampler.is_sampled(MAX_INT)
+    assert sampled
+    sampler = ConstSampler(False)
+    sampled, tags = sampler.is_sampled(1)
+    assert not sampled
+    sampled, tags = sampler.is_sampled(MAX_INT)
+    assert not sampled
+    assert tags == get_tags('const', False)
+    assert '%s' % sampler == 'ConstSampler(False)'
+
+
+def test_rate_limiting_sampler():
+    sampler = RateLimitingSampler(2)
+    sampler.rate_limiter.balance = 2.0
+    # stop time by overwriting timestamp() function to always return
+    # the same time
+    ts = time.time()
+    sampler.rate_limiter.last_tick = ts
+    with mock.patch('jaeger_client.rate_limiter.RateLimiter.timestamp') \
+            as mock_time:
+        mock_time.side_effect = lambda: ts  # always return same time
+        assert sampler.rate_limiter.timestamp() == ts
+        sampled, _ = sampler.is_sampled(0)
+        assert sampled, 'initial balance allows first item'
+        sampled, _ = sampler.is_sampled(0)
+        assert sampled, 'initial balance allows second item'
+        sampled, _ = sampler.is_sampled(0)
+        assert not sampled, 'initial balance exhausted'
+
+        # move time 250ms forward, not enough credits to pay for one sample
+        mock_time.side_effect = lambda: ts + 0.25
+        sampled, _ = sampler.is_sampled(0)
+        assert not sampled, 'not enough time passed for full item'
+
+        # move time 500ms forward, now enough credits to pay for one sample
+        mock_time.side_effect = lambda: ts + 0.5
+        sampled, _ = sampler.is_sampled(0)
+        assert sampled, 'enough time for new item'
+        sampled, _ = sampler.is_sampled(0)
+        assert not sampled, 'no more balance'
+
+        # move time 5s forward, enough to accumulate credits for 10 samples,
+        # but it should still be capped at 2
+        sampler.last_tick = ts  # reset the timer
+        mock_time.side_effect = lambda: ts + 5
+        sampled, _ = sampler.is_sampled(0)
+        assert sampled, 'enough time for new item'
+        sampled, _ = sampler.is_sampled(0)
+        assert sampled, 'enough time for second new item'
+        for i in range(0, 8):
+            sampled, tags = sampler.is_sampled(0)
+            assert not sampled, 'but no further, since time is stopped'
+        assert tags == get_tags('ratelimiting', 2)
+    sampler.close()
+    assert '%s' % sampler == 'RateLimitingSampler(2)'
+
+    # Test with rate limit of greater than 1 second
+    sampler = RateLimitingSampler(0.1)
+    sampler.rate_limiter.balance = 1.0
+    ts = time.time()
+    sampler.rate_limiter.last_tick = ts
+    with mock.patch('jaeger_client.rate_limiter.RateLimiter.timestamp') \
+            as mock_time:
+        mock_time.side_effect = lambda: ts  # always return same time
+        assert sampler.rate_limiter.timestamp() == ts
+        sampled, _ = sampler.is_sampled(0)
+        assert sampled, 'initial balance allows first item'
+        sampled, _ = sampler.is_sampled(0)
+        assert not sampled, 'initial balance exhausted'
+
+        # move time 11s forward, enough credits to pay for one sample
+        mock_time.side_effect = lambda: ts + 11
+        sampled, _ = sampler.is_sampled(0)
+        assert sampled
+    sampler.close()
+    assert '%s' % sampler == 'RateLimitingSampler(0.1)'
+
+    # Test update
+    sampler = RateLimitingSampler(3.0)
+    sampler.rate_limiter.balance = 3.0
+    ts = time.time()
+    sampler.rate_limiter.last_tick = ts
+    with mock.patch('jaeger_client.rate_limiter.RateLimiter.timestamp') \
+            as mock_time:
+        mock_time.side_effect = lambda: ts  # always return same time
+        assert sampler.rate_limiter.timestamp() == ts
+        sampled, _ = sampler.is_sampled(0)
+        assert sampled
+        assert sampler.rate_limiter.balance == 2.0
+        assert '%s' % sampler == 'RateLimitingSampler(3.0)'
+
+        sampler.update(3.0)
+        assert '%s' % sampler == \
+               'RateLimitingSampler(3.0)', 'should short cirtcuit if rate is the same'
+
+        sampler.update(2.0)
+        assert sampler.rate_limiter.balance == 4.0 / 3.0
+        assert '%s' % sampler == 'RateLimitingSampler(2.0)'
+    sampler.close()
+
+
+def test_guaranteed_throughput_probabilistic_sampler():
+    sampler = GuaranteedThroughputProbabilisticSampler('op',
+                                                       2,
+                                                       0.5)
+    sampler.lower_bound_sampler.rate_limiter.balance = 2.0
+    sampled, tags = sampler.is_sampled(MAX_INT - 10)
+    assert sampled
+    assert tags == get_tags('probabilistic', 0.5)
+    sampled, tags = sampler.is_sampled(MAX_INT + 10)
+    assert sampled
+    assert tags == get_tags('lowerbound', 0.5)
+    sampled, _ = sampler.is_sampled(MAX_INT + 10)
+    assert not sampled
+    assert '%s' % sampler == 'GuaranteedThroughputProbabilisticSampler(op, 0.500000, 2.000000)'
+
+    sampler.update(3, 0.51)
+    sampler.lower_bound_sampler.rate_limiter.balance = 3.0
+    sampled, tags = sampler.is_sampled(MAX_INT - 10)
+    assert sampled
+    assert tags == get_tags('probabilistic', 0.51)
+    sampled, tags = sampler.is_sampled(int(MAX_INT + (MAX_INT / 4)))
+    assert sampled
+    assert tags == get_tags('lowerbound', 0.51)
+
+    assert '%s' % sampler == 'GuaranteedThroughputProbabilisticSampler(op, 0.510000, 3.000000)'
+    sampler.close()
+
+
+def test_adaptive_sampler():
+    strategies = {
+        'defaultSamplingProbability': 0.51,
+        'defaultLowerBoundTracesPerSecond': 3,
+        'perOperationStrategies':
+        [
+            {
+                'operation': 'op',
+                'probabilisticSampling': {
+                    'samplingRate': 0.5
+                }
+            }
+        ]
+    }
+    sampler = AdaptiveSampler(strategies, 2)
+    sampled, tags = sampler.is_sampled(MAX_INT - 10, 'op')
+    assert sampled
+    assert tags == get_tags('probabilistic', 0.5)
+
+    # This operation is seen for the first time by the sampler
+    sampled, tags = sampler.is_sampled(MAX_INT - 10, 'new_op')
+    assert sampled
+    assert tags == get_tags('probabilistic', 0.51)
+
+    ts = time.time()
+    with mock.patch('jaeger_client.rate_limiter.RateLimiter.timestamp') \
+            as mock_time:
+
+        # Move time forward by a second to guarantee the rate limiter has enough credits
+        mock_time.side_effect = lambda: ts + 1
+
+        sampled, tags = sampler.is_sampled(int(MAX_INT + (MAX_INT / 4)), 'new_op')
+        assert sampled
+        assert tags == get_tags('lowerbound', 0.51)
+
+    # This operation is seen for the first time by the sampler but surpasses
+    # max_operations of 2. The default probabilistic sampler will be used
+    sampled, tags = sampler.is_sampled(MAX_INT - 10, 'new_op_2')
+    assert sampled
+    assert tags == get_tags('probabilistic', 0.51)
+    sampled, _ = sampler.is_sampled(int(MAX_INT + (MAX_INT / 4)), 'new_op_2')
+    assert not sampled
+    assert '%s' % sampler == 'AdaptiveSampler(0.510000, 3.000000, 2)'
+
+    # Update the strategies
+    strategies = {
+        'defaultSamplingProbability': 0.52,
+        'defaultLowerBoundTracesPerSecond': 4,
+        'perOperationStrategies':
+        [
+            {
+                'operation': 'op',
+                'probabilisticSampling': {
+                    'samplingRate': 0.52
+                }
+            },
+            {
+                'operation': 'new_op_3',
+                'probabilisticSampling': {
+                    'samplingRate': 0.53
+                }
+            }
+        ]
+    }
+    sampler.update(strategies)
+
+    # The probability for op has been updated
+    sampled, tags = sampler.is_sampled(MAX_INT - 10, 'op')
+    assert sampled
+    assert tags == get_tags('probabilistic', 0.52)
+
+    # A new operation has been added
+    sampled, tags = sampler.is_sampled(MAX_INT - 10, 'new_op_3')
+    assert sampled
+    assert tags == get_tags('probabilistic', 0.53)
+    assert '%s' % sampler == 'AdaptiveSampler(0.520000, 4.000000, 2)'
+
+    sampler.close()
+
+
+def test_adaptive_sampler_default_values():
+    adaptive_sampler = AdaptiveSampler({}, 2)
+    assert '%s' % adaptive_sampler == \
+           'AdaptiveSampler(0.001000, 0.001667, 2)', 'sampler should use default values'
+
+    sampled, tags = adaptive_sampler.is_sampled(0, 'op')
+    assert sampled
+    assert tags == \
+        get_tags('probabilistic', 0.001), 'should use default probability'
+    assert '%s' % adaptive_sampler.samplers['op'] == \
+           'GuaranteedThroughputProbabilisticSampler(op, 0.001000, 0.001667)'
+
+    adaptive_sampler.update(strategies={
+        'defaultLowerBoundTracesPerSecond': 4,
+        'perOperationStrategies':
+            [
+                {
+                    'operation': 'new_op',
+                    'probabilisticSampling': {
+                        'samplingRate': 0.002
+                    }
+                }
+            ]
+    })
+    assert '%s' % adaptive_sampler == 'AdaptiveSampler(0.001000, 4.000000, 2)'
+
+    sampled, tags = adaptive_sampler.is_sampled(0, 'new_op')
+    assert sampled
+    assert tags == get_tags('probabilistic', 0.002)
+    assert '%s' % adaptive_sampler.samplers['new_op'] == \
+           'GuaranteedThroughputProbabilisticSampler(new_op, 0.002000, 4.000000)'
+
+    sampled, tags = adaptive_sampler.is_sampled(0, 'op')
+    assert sampled
+    assert tags == get_tags('probabilistic', 0.001)
+    # TODO ruh roh, the lowerbound isn't changed
+    #  if the operation isn't included in perOperationStrategies
+    assert '%s' % adaptive_sampler.samplers['op'] == \
+           'GuaranteedThroughputProbabilisticSampler(op, 0.001000, 0.001667)'
+
+
+def test_sampler_equality():
+    const1 = ConstSampler(True)
+    const2 = ConstSampler(True)
+    const3 = ConstSampler(False)
+    assert const1 == const2
+    assert const1 != const3
+
+    prob1 = ProbabilisticSampler(rate=0.01)
+    prob2 = ProbabilisticSampler(rate=0.01)
+    prob3 = ProbabilisticSampler(rate=0.02)
+    assert prob1 == prob2
+    assert prob1 != prob3
+    assert const1 != prob1
+
+    rate1 = RateLimitingSampler(max_traces_per_second=0.01)
+    rate2 = RateLimitingSampler(max_traces_per_second=0.01)
+    rate3 = RateLimitingSampler(max_traces_per_second=0.02)
+    assert rate1 == rate2
+    assert rate1 != rate3
+    assert rate1 != const1
+    assert rate1 != prob1
+
+
+def test_remotely_controlled_sampler():
+    sampler = RemoteControlledSampler(
+        channel=mock.MagicMock(),
+        service_name='x'
+    )
+    sampled, tags = sampler.is_sampled(1)
+    assert sampled
+    assert tags == get_tags('probabilistic', DEFAULT_SAMPLING_PROBABILITY)
+
+    init_sampler = mock.MagicMock()
+    init_sampler.is_sampled = mock.MagicMock()
+    channel = mock.MagicMock()
+    channel.io_loop = None
+    sampler = RemoteControlledSampler(
+        channel=channel,
+        service_name='x',
+        init_sampler=init_sampler,
+        logger=mock.MagicMock(),
+    )
+    assert init_sampler.is_sampled.call_count == 1
+
+    sampler.is_sampled(1)
+    assert init_sampler.is_sampled.call_count == 2
+
+    sampler.io_loop = mock.MagicMock()
+    # noinspection PyProtectedMember
+    sampler._init_polling()
+    assert sampler.io_loop.call_later.call_count == 1
+
+    sampler._create_periodic_callback = mock.MagicMock()
+    # noinspection PyProtectedMember
+    sampler._delayed_polling()
+    sampler.close()
+
+    sampler = RemoteControlledSampler(
+        channel=mock.MagicMock(),
+        service_name='x',
+        max_operations=None,
+    )
+    assert sampler.max_operations == DEFAULT_MAX_OPERATIONS
+
+    sampler.close()
+    assert not sampler.running
+    sampler._init_polling()
+    assert not sampler.running
+    sampler._delayed_polling()
+    assert not sampler.running
+
+
+# noinspection PyProtectedMember
+def test_sampling_request_callback():
+    channel = mock.MagicMock()
+    channel.io_loop = mock.MagicMock()
+    error_reporter = mock.MagicMock()
+    error_reporter.error = mock.MagicMock()
+    sampler = RemoteControlledSampler(
+        channel=channel,
+        service_name='x',
+        error_reporter=error_reporter,
+        max_operations=10,
+    )
+
+    return_value = mock.MagicMock()
+    return_value.exception = lambda *args: False
+
+    probabilistic_strategy = """
+    {
+        "strategyType":"PROBABILISTIC",
+        "probabilisticSampling":
+        {
+            "samplingRate":0.002
+        }
+    }
+    """
+
+    return_value.result = lambda *args: \
+        type('obj', (object,), {'body': probabilistic_strategy})()
+    sampler._sampling_request_callback(return_value)
+    assert '%s' % sampler.sampler == \
+           'ProbabilisticSampler(0.002)', 'sampler should have changed to probabilistic'
+    prev_sampler = sampler.sampler
+
+    sampler._sampling_request_callback(return_value)
+    assert prev_sampler is sampler.sampler, \
+        "strategy hasn't changed so sampler should not change"
+
+    adaptive_sampling_strategy = """
+    {
+        "strategyType":"PROBABILISTIC",
+        "operationSampling":
+        {
+            "defaultSamplingProbability":0.001,
+            "defaultLowerBoundTracesPerSecond":2,
+            "perOperationStrategies":
+            [
+                {
+                    "operation":"op",
+                    "probabilisticSampling":{
+                        "samplingRate":0.002
+                    }
+                }
+            ]
+        }
+    }
+    """
+    return_value.result = lambda *args: \
+        type('obj', (object,), {'body': adaptive_sampling_strategy})()
+    sampler._sampling_request_callback(return_value)
+    assert '%s' % sampler.sampler == 'AdaptiveSampler(0.001000, 2.000000, 10)', \
+        'sampler should have changed to adaptive'
+    prev_sampler = sampler.sampler
+
+    sampler._sampling_request_callback(return_value)
+    assert prev_sampler is sampler.sampler, "strategy hasn't changed so sampler should not change"
+
+    probabilistic_strategy_bytes = probabilistic_strategy.encode('utf-8')
+
+    return_value.result = lambda *args: \
+        type('obj', (object,), {'body': probabilistic_strategy_bytes})()
+    sampler._sampling_request_callback(return_value)
+    assert '%s' % sampler.sampler == \
+           'ProbabilisticSampler(0.002)', 'sampler should have changed to probabilistic'
+
+    adaptive_sampling_strategy_bytearray = bytearray(adaptive_sampling_strategy.encode('utf-8'))
+
+    return_value.result = lambda *args: \
+        type('obj', (object,), {'body': adaptive_sampling_strategy_bytearray})()
+    sampler._sampling_request_callback(return_value)
+    assert '%s' % sampler.sampler == 'AdaptiveSampler(0.001000, 2.000000, 10)', \
+        'sampler should have changed to adaptive'
+    prev_sampler = sampler.sampler
+
+    return_value.exception = lambda *args: True
+    sampler._sampling_request_callback(return_value)
+    assert error_reporter.error.call_count == 1
+    assert prev_sampler is sampler.sampler, 'error fetching strategy should not update the sampler'
+
+    return_value.exception = lambda *args: False
+    return_value.result = lambda *args: type('obj', (object,), {'body': 'bad_json'})()
+
+    sampler._sampling_request_callback(return_value)
+    assert error_reporter.error.call_count == 2
+    assert prev_sampler is sampler.sampler, 'error updating sampler should not update the sampler'
+
+    return_value.result = lambda *args: \
+        type('obj', (object,), {'body': None})()
+    sampler._sampling_request_callback(return_value)
+    assert error_reporter.error.call_count == 3
+    assert prev_sampler is sampler.sampler, 'error updating sampler should not update the sampler'
+
+    return_value.result = lambda *args: \
+        type('obj', (object,), {'body': {'decode': None}})()
+    sampler._sampling_request_callback(return_value)
+    assert error_reporter.error.call_count == 4
+    assert prev_sampler is sampler.sampler, 'error updating sampler should not update the sampler'
+
+    return_value.result = lambda *args: \
+        type('obj', (object,), {'body': probabilistic_strategy})()
+    sampler._sampling_request_callback(return_value)
+    assert '%s' % sampler.sampler == 'ProbabilisticSampler(0.002)', \
+        'updating sampler from adaptive to probabilistic should work'
+
+    sampler.close()
+
+
+probabilistic_sampler = ProbabilisticSampler(0.002)
+other_probabilistic_sampler = ProbabilisticSampler(0.003)
+rate_limiting_sampler = RateLimitingSampler(10)
+other_rate_limiting_sampler = RateLimitingSampler(20)
+
+
+@pytest.mark.parametrize(
+    'response,init_sampler,expected_sampler,err_count,err_msg,reference_equivalence,max_operations',
+    [
+        (
+            {'strategyType': 'PROBABILISTIC', 'probabilisticSampling': {'samplingRate': 0.003}},
+            probabilistic_sampler,
+            other_probabilistic_sampler,
+            0,
+            'sampler should update to new probabilistic sampler',
+            False,
+            10,
+        ),
+        (
+            {'strategyType': 'PROBABILISTIC', 'probabilisticSampling': {'samplingRate': 400}},
+            probabilistic_sampler,
+            probabilistic_sampler,
+            1,
+            'sampler should remain the same if strategy is invalid',
+            True,
+            10,
+        ),
+        (
+            {'strategyType': 'PROBABILISTIC', 'probabilisticSampling': {'samplingRate': 0.002}},
+            probabilistic_sampler,
+            probabilistic_sampler,
+            0,
+            'sampler should remain the same with the same strategy',
+            True,
+            10,
+        ),
+        (
+            {'strategyType': 'RATE_LIMITING', 'rateLimitingSampling': {'maxTracesPerSecond': 10}},
+            probabilistic_sampler,
+            rate_limiting_sampler,
+            0,
+            'sampler should update to new rate limiting sampler',
+            False,
+            10,
+        ),
+        (
+            {'strategyType': 'RATE_LIMITING', 'rateLimitingSampling': {'maxTracesPerSecond': 10}},
+            rate_limiting_sampler,
+            rate_limiting_sampler,
+            0,
+            'sampler should remain the same with the same strategy',
+            True,
+            10,
+        ),
+        (
+            {'strategyType': 'RATE_LIMITING', 'rateLimitingSampling': {'maxTracesPerSecond': -10}},
+            rate_limiting_sampler,
+            rate_limiting_sampler,
+            1,
+            'sampler should remain the same if strategy is invalid',
+            True,
+            10,
+        ),
+        (
+            {'strategyType': 'RATE_LIMITING', 'rateLimitingSampling': {'maxTracesPerSecond': 20}},
+            rate_limiting_sampler,
+            other_rate_limiting_sampler,
+            0,
+            'sampler should update to new rate limiting sampler',
+            False,
+            10,
+        ),
+        (
+            {},
+            rate_limiting_sampler,
+            rate_limiting_sampler,
+            1,
+            'sampler should remain the same if strategy is empty',
+            True,
+            10,
+        ),
+        (
+            {'strategyType': 'INVALID_TYPE'},
+            rate_limiting_sampler,
+            rate_limiting_sampler,
+            1,
+            'sampler should remain the same if strategy is invalid',
+            True,
+            10,
+        ),
+        (
+            {'strategyType': 'INVALID_TYPE'},
+            rate_limiting_sampler,
+            rate_limiting_sampler,
+            1,
+            'sampler should remain the same if strategy is invalid',
+            True,
+            None,
+        ),
+    ]
+)
+def test_update_sampler(response, init_sampler, expected_sampler,
+                        err_count, err_msg, reference_equivalence, max_operations):
+    error_reporter = mock.MagicMock()
+    error_reporter.error = mock.MagicMock()
+    remote_sampler = RemoteControlledSampler(
+        channel=mock.MagicMock(),
+        service_name='x',
+        error_reporter=error_reporter,
+        max_operations=max_operations,
+        init_sampler=init_sampler,
+    )
+
+    # noinspection PyProtectedMember
+    remote_sampler._update_sampler(response)
+    assert error_reporter.error.call_count == err_count
+    if reference_equivalence:
+        assert remote_sampler.sampler is expected_sampler, err_msg
+    else:
+        assert remote_sampler.sampler == expected_sampler, err_msg
+
+    remote_sampler.close()
+
+
+# noinspection PyProtectedMember
+def test_update_sampler_adaptive_sampler():
+    error_reporter = mock.MagicMock()
+    error_reporter.error = mock.MagicMock()
+    remote_sampler = RemoteControlledSampler(
+        channel=mock.MagicMock(),
+        service_name='x',
+        error_reporter=error_reporter,
+        max_operations=10,
+    )
+
+    response = {
+        'strategyType': 'RATE_LIMITING',
+        'operationSampling':
+        {
+            'defaultSamplingProbability': 0.001,
+            'defaultLowerBoundTracesPerSecond': 2,
+            'perOperationStrategies':
+            [
+                {
+                    'operation': 'op',
+                    'probabilisticSampling': {
+                        'samplingRate': 0.002
+                    }
+                }
+            ]
+        }
+    }
+
+    remote_sampler._update_sampler(response)
+    assert '%s' % remote_sampler.sampler == 'AdaptiveSampler(0.001000, 2.000000, 10)'
+
+    new_response = {
+        'strategyType': 'RATE_LIMITING',
+        'operationSampling':
+        {
+            'defaultSamplingProbability': 0.51,
+            'defaultLowerBoundTracesPerSecond': 3,
+            'perOperationStrategies':
+            [
+                {
+                    'operation': 'op',
+                    'probabilisticSampling': {
+                        'samplingRate': 0.002
+                    }
+                }
+            ]
+        }
+    }
+
+    remote_sampler._update_sampler(new_response)
+    assert '%s' % remote_sampler.sampler == 'AdaptiveSampler(0.510000, 3.000000, 10)'
+
+    remote_sampler._update_sampler(
+        {'strategyType': 'PROBABILISTIC', 'probabilisticSampling': {'samplingRate': 0.004}})
+    assert '%s' % remote_sampler.sampler == 'ProbabilisticSampler(0.004)', \
+        'should not fail going from adaptive sampler to probabilistic sampler'
+
+    remote_sampler._update_sampler({'strategyType': 'RATE_LIMITING',
+                                    'operationSampling': {'defaultSamplingProbability': 0.4}})
+    assert '%s' % remote_sampler.sampler == 'AdaptiveSampler(0.400000, 0.001667, 10)'
+
+    remote_sampler.close()
+
+
+@pytest.mark.parametrize('strategy,expected', [
+    ({'probabilisticSampling': {'samplingRate': 0.003}}, 0.003),
+    ({}, 0.001),
+    (None, 0.001),
+    ({'probabilisticSampling': {}}, 0.001),
+    ({'probabilisticSampling': None}, 0.001),
+])
+def test_get_sampling_probability(strategy, expected):
+    assert expected == get_sampling_probability(strategy)
+
+
+@pytest.mark.parametrize('strategy,expected', [
+    ({'rateLimitingSampling': {'maxTracesPerSecond': 1}}, 1),
+    ({}, 0.0016666),
+    (None, 0.0016666),
+    ({'rateLimitingSampling': {}}, 0.0016666),
+    ({'rateLimitingSampling': None}, 0.0016666),
+])
+def test_get_rate_limit(strategy, expected):
+    assert math.fabs(expected - get_rate_limit(strategy)) < 0.0001

+ 255 - 0
desktop/core/ext-py/jaeger-client-4.0.0/tests/test_span.py

@@ -0,0 +1,255 @@
+# Copyright (c) 2016 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+
+import collections
+import json
+import mock
+
+from opentracing.ext import tags as ext_tags
+from jaeger_client import Span, SpanContext, ConstSampler
+
+
+def test_baggage():
+    mock_tracer = mock.MagicMock()
+    mock_tracer.max_tag_value_length = 100
+    ctx = SpanContext(trace_id=1, span_id=2, parent_id=None, flags=1)
+    span = Span(context=ctx, operation_name='x', tracer=mock_tracer)
+    assert span.get_baggage_item('x') is None
+    span.set_baggage_item('x', 'y').\
+        set_baggage_item('z', 'why')
+    assert span.get_baggage_item('x') == 'y'
+    assert span.get_baggage_item('z') == 'why'
+    assert span.get_baggage_item('tt') is None
+    assert len(span.context.baggage) == 2
+    span.set_baggage_item('x', 'b')  # override
+    assert span.get_baggage_item('x') == 'b'
+    assert len(span.context.baggage) == 2
+    span.set_baggage_item('X_y', '123')
+    assert span.get_baggage_item('X_y') == '123'
+    assert span.get_baggage_item('x-Y') is None
+    span.set_baggage_item('nonExistingKey', None).set_baggage_item('z', None)
+    assert 'z' not in span.context.baggage
+
+
+def _fields_to_dict(span_log):
+    return {f.key: f.vStr for f in span_log.fields}
+
+
+def test_baggage_logs():
+    mock_tracer = mock.MagicMock()
+    mock_tracer.max_tag_value_length = 100
+    ctx = SpanContext(trace_id=1, span_id=2, parent_id=None, flags=1)
+    span = Span(context=ctx, operation_name='x', tracer=mock_tracer)
+    span.set_baggage_item('x', 'a')
+    assert span.get_baggage_item('x') == 'a'
+    assert len(span.logs) == 1
+    assert _fields_to_dict(span.logs[0]) == {
+        'event': 'baggage', 'key': 'x', 'value': 'a',
+    }
+    span.set_baggage_item('x', 'b')  # override
+    assert span.get_baggage_item('x') == 'b'
+    assert len(span.logs) == 2
+    assert _fields_to_dict(span.logs[1]) == {
+        'event': 'baggage', 'key': 'x', 'value': 'b', 'override': 'true',
+    }
+    span.set_baggage_item('x', None)  # deletion
+    assert span.get_baggage_item('x') is None
+    assert len(span.logs) == 3
+    assert _fields_to_dict(span.logs[2]) == {
+        'event': 'baggage', 'key': 'x', 'value': 'None', 'override': 'true'
+    }
+
+
+def test_is_rpc():
+    mock_tracer = mock.MagicMock()
+    mock_tracer.max_tag_value_length = 100
+    ctx = SpanContext(trace_id=1, span_id=2, parent_id=None, flags=1)
+
+    span = Span(context=ctx, operation_name='x', tracer=mock_tracer)
+    assert span.is_rpc() is False
+    assert span.is_rpc_client() is False
+
+    span = Span(context=ctx, operation_name='x', tracer=mock_tracer)
+    span.set_tag(ext_tags.SPAN_KIND, ext_tags.SPAN_KIND_RPC_SERVER)
+    assert span.is_rpc() is True
+    assert span.is_rpc_client() is False
+
+    span = Span(context=ctx, operation_name='x', tracer=mock_tracer)
+    span.set_tag(ext_tags.SPAN_KIND, ext_tags.SPAN_KIND_RPC_CLIENT)
+    assert span.is_rpc() is True
+    assert span.is_rpc_client() is True
+
+
+def test_sampling_priority(tracer):
+    tracer.sampler = ConstSampler(False)
+    span = tracer.start_span(operation_name='x')
+    assert span.is_sampled() is False
+    span.set_tag(ext_tags.SAMPLING_PRIORITY, 1)
+    assert span.is_sampled()
+    assert span.is_debug()
+    span.set_tag(ext_tags.SAMPLING_PRIORITY, 1)
+    assert span.is_sampled()
+    assert span.is_debug()
+    span.set_tag(ext_tags.SAMPLING_PRIORITY, 0)
+    assert span.is_sampled() is False
+    span.set_tag(ext_tags.SAMPLING_PRIORITY, 'test')
+    assert span.is_sampled() is False
+
+
+def test_span_logging(tracer):
+    tpl = collections.namedtuple(
+        'Test',
+        ['method', 'args', 'kwargs', 'expected', 'error', 'timestamp'])
+
+    def test(method, expected,
+             args=None, kwargs=None, error=False, timestamp=None):
+        if isinstance(expected, str):
+            expected = {'event': expected}
+        return tpl(
+            method=method,
+            args=args if args else [],
+            expected=expected,
+            kwargs=kwargs if kwargs else {},
+            error=error,
+            timestamp=timestamp,
+        )
+
+    def event_payload(event, payload):
+        return {'event': event, 'payload': payload}
+
+    def from_json(val):
+        return json.loads(val)
+
+    tests = [
+        # deprecated info() method
+        test(method='info',
+             args=['msg'],
+             expected='msg'),
+        test(method='info',
+             args=['msg', 'data'],
+             expected=event_payload('msg', 'data')),
+        # deprecated error() method
+        test(method='error',
+             args=['msg'],
+             expected='msg', error=True),
+        test(method='error',
+             args=['msg', 'data'],
+             expected=event_payload('msg', 'data'), error=True),
+        # deprecated log_event() method
+        test(method='log_event',
+             args=['msg'],
+             expected='msg'),
+        test(method='log_event',
+             args=['msg', 'data'],
+             expected=event_payload('msg', 'data')),
+        # deprecated log() method
+        test(method='log',
+             kwargs={'event': 'msg'},
+             expected='msg'),
+        test(method='log',
+             kwargs={'event': 'msg', 'payload': 'data'},
+             expected=event_payload('msg', 'data')),
+        test(method='log',
+             kwargs={'event': 'msg', 'payload': 'data', 'ignored': 'blah'},
+             expected=event_payload('msg', 'data')),
+        test(method='log',
+             kwargs={'event': 'msg', 'payload': 'data', 'timestamp': 123},
+             expected=event_payload('msg', 'data'),
+             timestamp=123 * 1000 * 1000),  # in microseconds
+        # log_kv()
+        test(method='log_kv',
+             args=[{'event': 'msg'}],
+             expected='msg'),
+        test(method='log_kv',
+             args=[{'event': 'msg', 'x': 'y'}],
+             expected={'event': 'msg', 'x': 'y'}),
+        test(method='log_kv',
+             args=[{'event': 'msg', 'x': 'y'}, 123],  # all args positional
+             expected={'event': 'msg', 'x': 'y'},
+             timestamp=123 * 1000 * 1000),
+        test(method='log_kv',
+             args=[{'event': 'msg', 'x': 'y'}],  # positional and kwargs
+             kwargs={'timestamp': 123},
+             expected={'event': 'msg', 'x': 'y'},
+             timestamp=123 * 1000 * 1000),
+        test(method='log_kv',
+             args=[],  # kwargs only
+             kwargs={
+                 'key_values': {'event': 'msg', 'x': 'y'},
+                 'timestamp': 123,
+             },
+             expected={'event': 'msg', 'x': 'y'},
+             timestamp=123 * 1000 * 1000),  # to microseconds
+    ]
+
+    for test in tests:
+        name = '%s' % (test,)
+        span = tracer.start_span(operation_name='x')
+        span.logs = []
+        span.tags = []
+
+        if test.method == 'info':
+            span.info(*test.args, **test.kwargs)
+        elif test.method == 'error':
+            span.error(*test.args, **test.kwargs)
+        elif test.method == 'log':
+            span.log(*test.args, **test.kwargs)
+        elif test.method == 'log_event':
+            span.log_event(*test.args, **test.kwargs)
+        elif test.method == 'log_kv':
+            span.log_kv(*test.args, **test.kwargs)
+        else:
+            raise ValueError('Unknown method %s' % test.method)
+
+        assert len(span.logs) == 1, name
+        log = span.logs[0]
+        log_fields = _fields_to_dict(log)
+        assert log_fields == test.expected
+
+        if test.timestamp:
+            assert log.timestamp == test.timestamp
+
+
+def test_span_to_string(tracer):
+    tracer.service_name = 'unittest'
+    ctx = SpanContext(trace_id=1, span_id=1, parent_id=1, flags=1)
+    span = Span(context=ctx, operation_name='crypt', tracer=tracer)
+    assert '%s' % span == '1:1:1:1 unittest.crypt'
+
+
+def test_span_tag_value_max_length(tracer):
+    tracer.max_tag_value_length = 42
+    span = tracer.start_span(operation_name='x')
+    span.set_tag('x', 'x' * 50)
+    tag_n = len(span.tags) - 1
+    assert span.tags[tag_n].key == 'x'
+    assert span.tags[tag_n].vStr == 'x' * 42
+
+
+def test_span_tag_bool(tracer):
+    span = tracer.start_span(operation_name='y')
+    span.set_tag('y', True)
+    tag_n = len(span.tags) - 1
+    assert span.tags[tag_n].key == 'y'
+    assert span.tags[tag_n].vBool is True
+
+
+def test_span_tag_long(tracer):
+    span = tracer.start_span(operation_name='z')
+    span.set_tag('z', 200)
+    tag_n = len(span.tags) - 1
+    assert span.tags[tag_n].key == 'z'
+    assert span.tags[tag_n].vLong == 200

+ 49 - 0
desktop/core/ext-py/jaeger-client-4.0.0/tests/test_span_context.py

@@ -0,0 +1,49 @@
+# Copyright (c) 2016 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+
+from jaeger_client import SpanContext
+
+
+def test_parent_id_to_none():
+    ctx1 = SpanContext(trace_id=1, span_id=2, parent_id=0, flags=1)
+    assert ctx1.parent_id is None
+
+
+def test_with_baggage_items():
+    baggage1 = {'x': 'y'}
+    ctx1 = SpanContext(trace_id=1, span_id=2, parent_id=3, flags=1,
+                       baggage=baggage1)
+    ctx2 = ctx1.with_baggage_item('a', 'b')
+    assert ctx1.trace_id == ctx2.trace_id
+    assert ctx1.span_id == ctx2.span_id
+    assert ctx1.parent_id == ctx2.parent_id
+    assert ctx1.flags == ctx2.flags
+    assert ctx1.baggage != ctx2.baggage
+    baggage1['a'] = 'b'
+    assert ctx1.baggage == ctx2.baggage
+
+    ctx3 = ctx2.with_baggage_item('a', None)
+    assert ctx2.baggage != ctx3.baggage
+    baggage1.pop('a')
+    assert ctx3.baggage == baggage1
+
+
+def test_is_debug_id_container_only():
+    ctx = SpanContext.with_debug_id('value1')
+    assert ctx.is_debug_id_container_only
+    assert ctx.debug_id == 'value1'
+    ctx = SpanContext(trace_id=1, span_id=2, parent_id=3, flags=1)
+    assert not ctx.is_debug_id_container_only

+ 188 - 0
desktop/core/ext-py/jaeger-client-4.0.0/tests/test_thrift.py

@@ -0,0 +1,188 @@
+# Copyright (c) 2016 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from io import BytesIO
+
+import jaeger_client.thrift_gen.jaeger.ttypes as ttypes
+import jaeger_client.thrift_gen.sampling.SamplingManager as sampling_manager
+from opentracing import child_of, follows_from
+from jaeger_client import ProbabilisticSampler, RateLimitingSampler
+from jaeger_client import thrift, Span, SpanContext
+from jaeger_client.thrift_gen.agent import Agent as Agent
+from thrift.protocol.TCompactProtocol import TCompactProtocol
+from thrift.transport.TTransport import TMemoryBuffer
+
+
+def test_submit_batch(tracer):
+    span = tracer.start_span('test-span')
+    span.set_tag('bender', 'is great')
+    span.set_tag('peer.ipv4', 123123)
+    span.set_tag('unicode_val', u'non-ascii: \xe9')
+    span.set_tag(u'unicode_key_\xe9', 'ascii val')
+    span.log_event('kiss-my-shiny-metal-...')
+    span.finish()  # to get the duration defined
+    # verify that we can serialize the span
+    _marshall_span(span)
+
+
+def _marshall_span(span):
+    class TestTrans(TMemoryBuffer):
+        def now_reading(self):
+            """
+            Thrift TMemoryBuffer is not read-able AND write-able,
+            it's one or the other (really? yes.). This will convert
+            us from write-able to read-able.
+            """
+            self._buffer = BytesIO(self.getvalue())
+
+    batch = thrift.make_jaeger_batch(
+        spans=[span], process=ttypes.Process(serviceName='x', tags={}))
+
+    # write and read them to test encoding
+    args = Agent.emitBatch_args(batch)
+    t = TestTrans()
+    prot = TCompactProtocol(t)
+    args.write(prot)
+    t.now_reading()
+    args.read(prot)
+
+
+def test_large_ids(tracer):
+
+    def serialize(trace_id, span_id):
+        """Checks that there are no exceptions during marshalling."""
+        parent_ctx = SpanContext(trace_id=trace_id, span_id=span_id,
+                                 parent_id=0, flags=1)
+        parent = Span(context=parent_ctx, operation_name='x', tracer=tracer)
+        span = tracer.start_span(operation_name='x',
+                                 references=child_of(parent.context))
+        span.finish()
+        _marshall_span(span)
+
+    trace_id = 0x77fd53dc6b437681
+    serialize(trace_id, trace_id)
+    assert thrift.id_to_int(trace_id) == 0x77fd53dc6b437681
+
+    trace_id = 0x7fffffffffffffff
+    serialize(trace_id, trace_id)
+    assert thrift.id_to_int(trace_id) == 0x7fffffffffffffff
+
+    trace_id = 0x8000000000000000
+    serialize(trace_id, trace_id)
+    assert thrift.id_to_int(trace_id) == -0x8000000000000000
+
+    trace_id = 0x97fd53dc6b437681
+    serialize(trace_id, trace_id)
+
+    trace_id = (1 << 64) - 1
+    assert trace_id == 0xffffffffffffffff
+    serialize(trace_id, trace_id)
+    assert thrift.id_to_int(trace_id) == -1
+
+    trace_id = (1 << 128) - 1
+    span_id = 0xffffffffffffffff
+    assert trace_id == 0xffffffffffffffffffffffffffffffff
+    serialize(trace_id, span_id)
+    assert thrift._id_to_low(trace_id) == 0xffffffffffffffff
+    assert thrift._id_to_high(trace_id) == 0xffffffffffffffff
+
+    trace_id = 0xfb34678b8864f051e5c8c603484e57
+    span_id = 0x77fd53dc6b437681
+    serialize(trace_id, span_id)
+    assert thrift._id_to_low(trace_id) == 0x51e5c8c603484e57
+    assert thrift._id_to_high(trace_id) == 0xfb34678b8864f0
+
+
+def test_none_ids():
+    assert thrift.id_to_int(None) is None
+    assert thrift._id_to_low(None) is None
+    assert thrift._id_to_high(None) is None
+
+
+def test_large_tags():
+    tag = thrift.make_tag('x', 'y' * 300, max_length=256)
+    assert len(tag.vStr) <= 256
+
+
+def test_bool_tags():
+    tag = thrift.make_tag('booltag', True, max_length=256)
+    assert tag.vBool is True
+
+
+def test_bool_tags_false():
+    tag = thrift.make_tag('booltag', False, max_length=256)
+    assert tag.vBool is False
+
+
+def test_long_tags():
+    tag = thrift.make_tag('longtag', 404, max_length=256)
+    assert tag.vLong == 404
+
+
+def test_double_tags():
+    tag = thrift.make_tag('doubletag', 12.1, max_length=256)
+    assert tag.vDouble == 12.1
+
+
+def test_parse_sampling_strategy():
+    # probabilistic
+
+    resp = sampling_manager.SamplingStrategyResponse(
+        strategyType=sampling_manager.SamplingStrategyType.PROBABILISTIC)
+    s, e = thrift.parse_sampling_strategy(response=resp)
+    assert s is None and e is not None
+
+    resp.probabilisticSampling = \
+        sampling_manager.ProbabilisticSamplingStrategy(samplingRate=2)
+    s, e = thrift.parse_sampling_strategy(response=resp)
+    assert s is None and e is not None
+
+    resp.probabilisticSampling = \
+        sampling_manager.ProbabilisticSamplingStrategy(samplingRate=0.5)
+    s, e = thrift.parse_sampling_strategy(response=resp)
+    assert isinstance(s, ProbabilisticSampler) and e is None
+
+    # rate limiting
+
+    resp = sampling_manager.SamplingStrategyResponse(
+        strategyType=sampling_manager.SamplingStrategyType.RATE_LIMITING)
+    s, e = thrift.parse_sampling_strategy(response=resp)
+    assert s is None and e is not None
+
+    resp.rateLimitingSampling = \
+        sampling_manager.RateLimitingSamplingStrategy(maxTracesPerSecond=-1)
+    s, e = thrift.parse_sampling_strategy(response=resp)
+    assert s is None and e is not None
+
+    resp.rateLimitingSampling = \
+        sampling_manager.RateLimitingSamplingStrategy(maxTracesPerSecond=1)
+    s, e = thrift.parse_sampling_strategy(response=resp)
+    assert isinstance(s, RateLimitingSampler) and e is None
+
+    # wrong strategy type
+
+    resp.strategyType = 'x'
+    s, e = thrift.parse_sampling_strategy(response=resp)
+    assert s is None and e is not None
+
+
+def test_parse_span_references(tracer):
+    span = tracer.start_span('test')
+    span2 = tracer.start_span('test2')
+    follow_span = tracer.start_span('test-follow', references=[follows_from(span.context),
+                                                               child_of(span2.context)])
+    span.finish()
+    span2.finish()
+    follow_span.finish()
+    _marshall_span(follow_span)

+ 101 - 0
desktop/core/ext-py/jaeger-client-4.0.0/tests/test_throttler.py

@@ -0,0 +1,101 @@
+# Copyright (c) 2018 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import mock
+import time
+
+from jaeger_client.throttler import RemoteThrottler
+
+
+def test_throttler_simple():
+    channel = mock.MagicMock()
+    throttler = RemoteThrottler(channel, 'test-service')
+    allowed = throttler.is_allowed('test-operation')
+    assert not allowed
+    allowed = throttler.is_allowed('test-operation')
+    assert not allowed
+
+
+def test_throttler_no_io_loop():
+    channel = mock.MagicMock()
+    channel.io_loop = None
+    throttler = RemoteThrottler(channel, 'test-service')
+    assert throttler
+
+
+def test_throttler_credits():
+    channel = mock.MagicMock()
+    throttler = RemoteThrottler(channel, 'test-service')
+    throttler.credits['test-operation'] = 3.0
+    allowed = throttler.is_allowed('test-operation')
+    assert allowed
+    assert throttler.credits['test-operation'] == 2.0
+
+
+def test_throttler_init_polling():
+    channel = mock.MagicMock()
+    throttler = RemoteThrottler(channel, 'test-service')
+    # noinspection PyProtectedMember
+    throttler._init_polling()
+    assert channel.io_loop.call_later.call_count == 1
+    throttler.close()
+    # noinspection PyProtectedMember
+    throttler._init_polling()
+    assert channel.io_loop.call_later.call_count == 1
+
+
+def test_throttler_delayed_polling():
+    channel = mock.MagicMock()
+    channel.io_loop.time = time.time
+    channel.io_loop._next_timeout = 1
+    throttler = RemoteThrottler(channel, 'test-service')
+    throttler.credits = {'test-operation': 0}
+    # noinspection PyProtectedMember
+    throttler._delayed_polling()
+    assert channel.request_throttling_credits.call_count == 1
+    assert throttler.periodic
+    throttler.close()
+    throttler.periodic = None
+    throttler._delayed_polling()
+    assert throttler.periodic is None
+
+
+def test_throttler_request_callback():
+    channel = mock.MagicMock()
+    throttler = RemoteThrottler(channel, 'test-service')
+    throttler.error_reporter = mock.MagicMock()
+    # noinspection PyProtectedMember
+    future = mock.MagicMock()
+    future.exception = mock.MagicMock()
+    future.exception.return_value = Exception()
+    throttler._request_callback(future)
+    assert throttler.error_reporter.error.call_count == 1
+    future.exception.return_value = None
+    future.result.return_value = mock.MagicMock()
+    future.result.return_value.body = """
+        {
+            \"balances\": [
+                {
+                    \"operation\": \"test-operation\",
+                    \"balance\": 2.0
+                }
+            ]
+        }
+    """
+    throttler._request_callback(future)
+    assert throttler.credits['test-operation'] == 2.0
+
+    future.result.return_value.body = '{ "bad": "json" }'
+    throttler._request_callback(future)
+    assert throttler.error_reporter.error.call_count == 2

+ 361 - 0
desktop/core/ext-py/jaeger-client-4.0.0/tests/test_tracer.py

@@ -0,0 +1,361 @@
+# Copyright (c) 2016 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import mock
+import random
+import six
+import socket
+
+import pytest
+import tornado.httputil
+
+from opentracing import Format, child_of, follows_from
+from opentracing.ext import tags as ext_tags
+from jaeger_client import ConstSampler, SpanContext, Tracer
+from jaeger_client import constants as c
+
+
+def find_tag(span, key, tag_type='str'):
+    for tag in span.tags:
+        if tag.key == key:
+            if tag_type == 'str':
+                return tag.vStr
+            elif tag_type == 'bool':
+                return tag.vBool
+    return None
+
+
+def test_start_trace(tracer):
+    assert type(tracer) is Tracer
+    with mock.patch.object(random.Random, 'getrandbits') as mock_random, \
+            mock.patch('time.time') as mock_timestamp:
+        mock_random.return_value = 12345
+        mock_timestamp.return_value = 54321
+
+        span = tracer.start_span('test')
+        span.set_tag(ext_tags.SPAN_KIND, ext_tags.SPAN_KIND_RPC_SERVER)
+        assert span, 'Span must not be nil'
+        assert span.tracer == tracer, 'Tracer must be referenced from span'
+        assert find_tag(span, 'span.kind') == ext_tags.SPAN_KIND_RPC_SERVER, \
+            'Span must be server-side'
+        assert span.trace_id == 12345, 'Must match trace_id'
+        assert span.is_sampled(), 'Must be sampled'
+        assert span.parent_id is None, 'Must not have parent_id (root span)'
+        assert span.start_time == 54321, 'Must match timestamp'
+
+        span.finish()
+        assert span.end_time is not None, 'Must have end_time defined'
+        tracer.reporter.assert_called_once()
+
+    tracer.close()
+
+
+def test_forced_sampling(tracer):
+    tracer.sampler = ConstSampler(False)
+    span = tracer.start_span('test2',
+                             tags={ext_tags.SAMPLING_PRIORITY: 1})
+    assert span.is_sampled()
+    assert span.is_debug()
+
+
+@pytest.mark.parametrize('mode,', ['arg', 'ref'])
+def test_start_child(tracer, mode):
+    root = tracer.start_span('test')
+    if mode == 'arg':
+        span = tracer.start_span('test', child_of=root.context)
+    elif mode == 'ref':
+        span = tracer.start_span('test', references=child_of(root.context))
+    else:
+        raise ValueError('bad mode')
+    span.set_tag(ext_tags.SPAN_KIND, ext_tags.SPAN_KIND_RPC_SERVER)
+    assert span.is_sampled(), 'Must be sampled'
+    assert span.trace_id == root.trace_id, 'Must have the same trace id'
+    assert span.parent_id == root.span_id, 'Must inherit parent id'
+    span.finish()
+    assert span.end_time is not None, 'Must have end_time set'
+    tracer.reporter.assert_called_once()
+    tracer.close()
+
+
+@pytest.mark.parametrize('one_span_per_rpc,', [True, False])
+def test_one_span_per_rpc(tracer, one_span_per_rpc):
+    tracer.one_span_per_rpc = one_span_per_rpc
+    span = tracer.start_span('client')
+    span.set_tag(ext_tags.SPAN_KIND, ext_tags.SPAN_KIND_RPC_CLIENT)
+    child = tracer.start_span(
+        'server',
+        references=child_of(span.context),
+        tags={ext_tags.SPAN_KIND: ext_tags.SPAN_KIND_RPC_SERVER},
+    )
+    assert span.trace_id == child.trace_id, 'Must have the same trace ids'
+    if one_span_per_rpc:
+        assert span.span_id == child.span_id, 'Must have the same span ids'
+    else:
+        assert span.span_id != child.span_id, 'Must have different span ids'
+
+
+def test_child_span(tracer):
+    span = tracer.start_span('test')
+    child = tracer.start_span('child', references=child_of(span.context))
+    child.set_tag(ext_tags.SPAN_KIND, ext_tags.SPAN_KIND_RPC_CLIENT)
+    child.set_tag('bender', 'is great')
+    child.log_event('kiss-my-shiny-metal-...')
+    child.finish()
+    span.finish()
+    tracer.reporter.report_span.assert_called_once()
+    assert len(span.logs) == 0, 'Parent span is Local, must not have events'
+    assert len(child.logs) == 1, 'Child must have one events'
+
+    tracer.sampler = ConstSampler(False)
+    span = tracer.start_span('test')
+    child = tracer.start_span('child', references=child_of(span.context))
+    child.set_tag('bender', 'is great')
+    child.log_event('kiss-my-shiny-metal-...')
+    child.finish()
+    span.finish()
+    assert len(child.logs) == 0, 'Must have no events, not sampled'
+    assert len(child.tags) == 0, 'Must have no attributes, not sampled'
+    tracer.close()
+
+
+def test_follows_from(tracer):
+    span = tracer.start_span('test')
+    span1 = tracer.start_span('test2')
+    follow_span = tracer.start_span('follow-span', references=[follows_from(span.context),
+                                                               follows_from(span1.context),
+                                                               follows_from(None)])
+    span.finish()
+    span1.finish()
+    follow_span.finish()
+    tracer.reporter.report_span.assert_called_once()
+    assert len(follow_span.references) == 2
+    assert follow_span.context.parent_id == span.context.span_id
+    for reference in follow_span.references:
+        assert reference.referenced_context is not None
+
+    span = tracer.start_span('test')
+    follow_span = tracer.start_span(references=follows_from(span.context))
+    span.finish()
+    follow_span.finish()
+    tracer.reporter.report_span.assert_called_once()
+    assert isinstance(follow_span.references, list)
+
+    span = tracer.start_span('test')
+    parent_span = tracer.start_span('test-parent')
+    child_span = tracer.start_span('test-child', child_of=parent_span,
+                                   references=follows_from(span.context))
+    span.finish()
+    parent_span.finish()
+    child_span.finish()
+    tracer.reporter.report_span.assert_called_once()
+    assert child_span.context.parent_id == parent_span.context.span_id
+    assert len(child_span.references) == 1
+    tracer.close()
+
+
+def test_sampler_effects(tracer):
+    tracer.sampler = ConstSampler(True)
+    span = tracer.start_span('test')
+    assert span.is_sampled(), 'Must be sampled'
+
+    tracer.sampler = ConstSampler(False)
+    span = tracer.start_span('test')
+    assert not span.is_sampled(), 'Must not be sampled'
+    tracer.close()
+
+
+@pytest.mark.parametrize('inject_mode', ['span', 'context'])
+def test_serialization(tracer, inject_mode):
+    span = tracer.start_span('help')
+    carrier = {}
+    if inject_mode == 'span':
+        injectable = span
+    elif inject_mode == 'context':
+        injectable = span.context
+    else:
+        raise ValueError('bad inject_mode')
+    tracer.inject(
+        span_context=injectable, format=Format.TEXT_MAP, carrier=carrier
+    )
+    assert len(carrier) > 0
+    h_ctx = tornado.httputil.HTTPHeaders(carrier)
+    assert 'UBER-TRACE-ID' in h_ctx
+    ctx2 = tracer.extract(Format.TEXT_MAP, carrier)
+    assert ctx2 is not None
+    assert ctx2.trace_id == span.trace_id
+    assert ctx2.span_id == span.span_id
+    assert ctx2.parent_id == span.parent_id
+    assert ctx2.flags == span.flags
+
+
+def test_serialization_error(tracer):
+    span = 'span'
+    carrier = {}
+    with pytest.raises(ValueError):
+        tracer.inject(
+            span_context=span, format=Format.TEXT_MAP, carrier=carrier
+        )
+
+
+def test_tracer_tags():
+    reporter = mock.MagicMock()
+    sampler = ConstSampler(True)
+
+    with mock.patch('socket.gethostname', return_value='dream-host.com'):
+        t = Tracer(service_name='x', reporter=reporter, sampler=sampler)
+        assert t.tags.get('hostname') == 'dream-host.com'
+        assert 'ip' in t.tags
+        assert 'jaeger.version' in t.tags
+
+
+def test_tracer_tags_passed_to_reporter():
+    reporter = mock.MagicMock()
+    reporter.set_process = mock.MagicMock()
+    sampler = ConstSampler(True)
+    tracer = Tracer(
+        service_name='x', reporter=reporter, sampler=sampler,
+        max_tag_value_length=123,
+    )
+    reporter.set_process.assert_called_once_with(
+        service_name='x', tags=tracer.tags, max_length=123,
+    )
+
+
+def test_tracer_tags_no_hostname():
+    reporter = mock.MagicMock()
+    sampler = ConstSampler(True)
+
+    from jaeger_client.tracer import logger
+    with mock.patch.object(logger, 'exception') as mock_log:
+        with mock.patch('socket.gethostname',
+                        side_effect=['host', socket.timeout()]):
+            Tracer(service_name='x', reporter=reporter, sampler=sampler)
+        assert mock_log.call_count == 1
+
+
+@pytest.mark.parametrize('span_type,expected_tags', [
+    ('root', {
+        'sampler.type': 'const',
+        'sampler.param': True,
+    }),
+    ('child', {
+        'sampler.type': None,
+        'sampler.param': None,
+    }),
+    ('rpc-server', {
+        'sampler.type': None,
+        'sampler.param': None,
+    }),
+])
+def test_tracer_tags_on_root_span(span_type, expected_tags):
+    reporter = mock.MagicMock()
+    sampler = ConstSampler(True)
+    with mock.patch('socket.gethostname', return_value='dream-host.com'):
+        tracer = Tracer(service_name='x',
+                        reporter=reporter,
+                        sampler=sampler,
+                        tags={'global-tag': 'global-tag'})
+        span = tracer.start_span(operation_name='root')
+        if span_type == 'child':
+            span = tracer.start_span('child', child_of=span)
+        if span_type == 'rpc-server':
+            span = tracer.start_span(
+                'child', child_of=span.context,
+                tags={ext_tags.SPAN_KIND: ext_tags.SPAN_KIND_RPC_SERVER}
+            )
+        for key, value in six.iteritems(expected_tags):
+            found_tag = find_tag(span, key, type(value).__name__)
+            if value is None:
+                assert found_tag is None, 'test (%s)' % span_type
+                continue
+
+            assert found_tag == value, \
+                'test (%s): expecting tag %s=%s' % (span_type, key, value)
+
+
+def test_tracer_override_codecs():
+    reporter = mock.MagicMock()
+    sampler = ConstSampler(True)
+    codecs = {
+        'extra_codec': 'codec_placeholder',
+        Format.BINARY: 'overridden_binary_codec'
+
+    }
+    with mock.patch('socket.gethostname', return_value='dream-host.com'):
+        tracer = Tracer(service_name='x', reporter=reporter, sampler=sampler,
+                        extra_codecs=codecs)
+        assert tracer.codecs['extra_codec'] == 'codec_placeholder',\
+                                               'Extra codec not found'
+        assert tracer.codecs[Format.BINARY] == 'overridden_binary_codec',\
+                                               'Binary format codec not overridden'
+
+
+def test_tracer_hostname_tag():
+    reporter = mock.MagicMock()
+    sampler = ConstSampler(True)
+    tracer = Tracer(
+        service_name='x',
+        tags={c.JAEGER_HOSTNAME_TAG_KEY: 'jaeger-client-app.local'},
+        reporter=reporter,
+        sampler=sampler,
+    )
+
+    assert tracer.tags[c.JAEGER_HOSTNAME_TAG_KEY] == 'jaeger-client-app.local'
+
+
+def test_tracer_ip_tag():
+    reporter = mock.MagicMock()
+    sampler = ConstSampler(True)
+    tracer = Tracer(
+        service_name='x',
+        tags={c.JAEGER_IP_TAG_KEY: '192.0.2.3'},
+        reporter=reporter,
+        sampler=sampler,
+    )
+
+    assert tracer.tags[c.JAEGER_IP_TAG_KEY] == '192.0.2.3'
+
+
+def test_tracer_throttler():
+    tracer = Tracer(
+        service_name='x',
+        reporter=mock.MagicMock(),
+        sampler=mock.MagicMock(),
+        throttler=mock.MagicMock())
+    tracer.throttler.is_allowed.return_value = True
+    assert tracer.is_debug_allowed()
+    tracer.throttler.is_allowed.return_value = False
+    assert not tracer.is_debug_allowed()
+    debug_span_context = SpanContext.with_debug_id('debug-id')
+    span = tracer.start_span('test-operation', child_of=debug_span_context)
+    assert not span.is_debug()
+
+
+def test_tracer_128bit_trace_id():
+    reporter = mock.MagicMock()
+    sampler = mock.MagicMock()
+    tracer = Tracer(
+        service_name='x',
+        reporter=reporter,
+        sampler=sampler,
+    )
+    assert tracer.max_trace_id_bits == c._max_id_bits
+
+    tracer = Tracer(
+        service_name='x',
+        reporter=reporter,
+        sampler=sampler,
+        generate_128bit_trace_id=True,
+    )
+    assert tracer.max_trace_id_bits == c._max_trace_id_bits

+ 92 - 0
desktop/core/ext-py/jaeger-client-4.0.0/tests/test_tracer_benchmark.py

@@ -0,0 +1,92 @@
+# Copyright (c) 2016 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from six.moves import range
+import time
+from opentracing import Tracer as NoopTracer
+from jaeger_client.tracer import Tracer
+from jaeger_client.reporter import NullReporter
+from jaeger_client.sampler import ConstSampler
+
+
+def _generate_spans(tracer, iterations=1000, sleep=None):
+    for i in range(0, iterations):
+        span = tracer.start_trace(operation_name='root-span')
+        span.finish()
+        if sleep is not None:
+            t = time.time() + sleep
+            while time.time() < t:
+                pass
+
+
+def test_noop_tracer(benchmark):
+    tracer = NoopTracer()
+    benchmark(_generate_spans, tracer)
+
+
+def test_no_sampling(benchmark):
+    tracer = Tracer.default_tracer(
+        channel=None, service_name='benchmark',
+        reporter=NullReporter(), sampler=ConstSampler(False))
+    benchmark(_generate_spans, tracer)
+
+
+def test_100pct_sampling(benchmark):
+    tracer = Tracer.default_tracer(
+        channel=None, service_name='benchmark',
+        reporter=NullReporter(), sampler=ConstSampler(True))
+    benchmark(_generate_spans, tracer)
+
+
+def test_100pct_sampling_250mcs(benchmark):
+    tracer = Tracer.default_tracer(
+        channel=None, service_name='benchmark',
+        reporter=NullReporter(), sampler=ConstSampler(True))
+    # 250 micros for request execution
+    benchmark(_generate_spans, tracer, sleep=0.00025)
+
+
+def test_all_batched_size10(benchmark):
+    from tchannel.sync import TChannel
+    ch = TChannel(name='foo')
+    f = ch.advertise(routers=['127.0.0.1:21300', '127.0.0.1:21301'])
+    f.result()
+    tracer = Tracer.default_tracer(ch, service_name='benchmark',
+                                   sampler=ConstSampler(True))
+    tracer.reporter.batch_size = 10
+    # 250 micros for request execution
+    benchmark(_generate_spans, tracer, sleep=0.00025)
+
+
+def test_all_batched_size5(benchmark):
+    from tchannel.sync import TChannel
+    ch = TChannel(name='foo')
+    f = ch.advertise(routers=['127.0.0.1:21300', '127.0.0.1:21301'])
+    f.result()
+    tracer = Tracer.default_tracer(ch, service_name='benchmark',
+                                   sampler=ConstSampler(True))
+    tracer.reporter.batch_size = 5
+    # 250 micros for request execution
+    benchmark(_generate_spans, tracer, sleep=0.00025)
+
+
+def test_all_not_batched(benchmark):
+    from tchannel.sync import TChannel
+    ch = TChannel(name='foo')
+    f = ch.advertise(routers=['127.0.0.1:21300', '127.0.0.1:21301'])
+    f.result()
+    tracer = Tracer.default_tracer(ch, service_name='benchmark', sampler=ConstSampler(True))
+    tracer.reporter.batch_size = 1
+    # 250 micros for request execution
+    benchmark(_generate_spans, tracer, sleep=0.00025)

+ 83 - 0
desktop/core/ext-py/jaeger-client-4.0.0/tests/test_utils.py

@@ -0,0 +1,83 @@
+# Copyright (c) 2016 Uber Technologies, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+
+import mock
+import unittest
+
+from jaeger_client import utils
+
+
+class ConfigTests(unittest.TestCase):
+
+    def check_boolean(self, string, default, correct):
+        assert utils.get_boolean(string, default) == correct
+
+    def test_get_false_boolean(self):
+        self.check_boolean('false', 'asdf', False)
+
+    def test_get_0_boolean(self):
+        self.check_boolean('0', 'asdf', False)
+
+    def test_get_true_boolean(self):
+        self.check_boolean('true', 'qwer', True)
+
+    def test_get_1_boolean(self):
+        self.check_boolean('1', 'qwer', True)
+
+    def test_get_unknown_boolean(self):
+        self.check_boolean('zxcv', 'qwer', 'qwer')
+
+    def test_get_None_boolean(self):
+        self.check_boolean(None, 'qwer', False)
+
+#    def test_error_reporter_doesnt_send_metrics_if_not_configured(self):
+#        er = utils.ErrorReporter(False)
+#        er.error('foo', 1)
+#        assert not mock_metrics.count.called
+
+    def test_error_reporter_sends_metrics_if_configured(self):
+        mock_metrics = mock.MagicMock()
+        er = utils.ErrorReporter(mock_metrics)
+        er.error('foo', 1)
+        assert mock_metrics.count.called_with('foo', 1)
+
+    def test_error_reporter_doesnt_send_log_messages_if_before_deadline(self):
+        mock_logger = mock.MagicMock()
+        er = utils.ErrorReporter(None, logger=mock_logger, log_interval_minutes=1000)
+        er.error('foo', 1)
+        assert not mock_logger.error.called
+
+    def test_error_reporter_sends_log_messages_if_after_deadline(self):
+        mock_logger = mock.MagicMock()
+        # 0 log interval means we're always after the deadline, so always log
+        er = utils.ErrorReporter(None, logger=mock_logger, log_interval_minutes=0)
+        er._last_error_reported_at = 0
+        er.error('foo', 1, 'error args')
+        assert mock_logger.error.call_args == (('foo', 1, 'error args',),)
+
+
+def test_local_ip_does_not_blow_up():
+    import socket
+    import jaeger_client.utils
+    socket.gethostname()
+    with mock.patch('socket.gethostbyname',
+                    side_effect=[IOError(), '127.0.0.1']):
+        jaeger_client.utils.local_ip()
+
+
+def test_get_local_ip_by_socket_does_not_blow_up():
+    import jaeger_client.utils
+    jaeger_client.utils.get_local_ip_by_socket()