-
Notifications
You must be signed in to change notification settings - Fork 477
Matt/mongo net #31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Matt/mongo net #31
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
6de7e6b
clean
clutchski 9d5107a
WIP mongo
clutchski 5e1fe39
wip
clutchski 00f61ff
mongo tests
clutchski 9e2e854
mongo: D.R.Y.
clutchski 650e3ac
mongo: add delete tests
clutchski 8f03cc5
mongo: wip of sniffing the mongo network
clutchski 45f2c19
mongo: add insert_many tracing
clutchski 9fd3052
span: sort tags in pprint
clutchski a6305d7
mongo: delete unused code
clutchski d5cf98e
mongo: re-use resource generation code
clutchski 306a70e
mongo: more cleanup
clutchski 5d3995c
pymongo: try to get working on circle
clutchski a9263eb
pymongo: docs
clutchski 949cdb3
mongo: protect imports
clutchski aa5897b
fix circle docker cmd
clutchski 0e27dfa
mongo: handle updates
clutchski 215427e
mongo: don't double bind circle port
clutchski d5054f5
mongo: fix python3 issues
clutchski 1de1687
mongo: more python3 fun
clutchski 60d9674
pymongo: clean up docs
clutchski File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| """ | ||
| The pymongo integration works by wrapping pymongo's MongoClient to trace | ||
| network calls. Basic usage:: | ||
|
|
||
| from pymongo import MongoClient | ||
| from ddtrace import tracer | ||
| from ddtrace.contrib.pymongo import trace_mongo_client | ||
|
|
||
| client = trace_mongo_client( | ||
| MongoClient(), tracer, "my-mongo-db") | ||
|
|
||
| db = client["test-db"] | ||
| db.teams.find({"name": "Toronto Maple Leafs"}) | ||
| """ | ||
|
|
||
| from ..util import require_modules | ||
|
|
||
| required_modules = ['pymongo'] | ||
|
|
||
| with require_modules(required_modules) as missing_modules: | ||
| if not missing_modules: | ||
| from .trace import trace_mongo_client | ||
| __all__ = ['trace_mongo_client'] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
|
|
||
|
|
||
| class Command(object): | ||
| """ Command stores information about a pymongo network command, """ | ||
|
|
||
| __slots__ = ['name', 'coll', 'tags', 'metrics', 'query'] | ||
|
|
||
| def __init__(self, name, coll): | ||
| self.name = name | ||
| self.coll = coll | ||
| self.tags = {} | ||
| self.metrics = {} | ||
| self.query = None | ||
|
|
||
|
|
||
| def parse_query(query): | ||
| """ Return a command parsed from the given mongo db query. """ | ||
| cmd = Command("query", query.coll) | ||
| cmd.query = query.spec | ||
| return cmd | ||
|
|
||
| def parse_spec(spec): | ||
| """ Return a Command that has parsed the relevant detail for the given | ||
| pymongo SON spec. | ||
| """ | ||
|
|
||
| # the first element is the command and collection | ||
| items = list(spec.items()) | ||
| if not items: | ||
| return None | ||
| name, coll = items[0] | ||
| cmd = Command(name, coll) | ||
|
|
||
| if 'ordered' in spec: # in insert and update | ||
| cmd.tags['mongodb.ordered'] = spec['ordered'] | ||
|
|
||
| if cmd.name == 'insert': | ||
| if 'documents' in spec: | ||
| cmd.metrics['mongodb.documents'] = len(spec['documents']) | ||
|
|
||
| elif cmd.name == 'update': | ||
| updates = spec.get('updates') | ||
| if updates: | ||
| # FIXME[matt] is there ever more than one here? | ||
| cmd.query = updates[0].get("q") | ||
|
|
||
| elif cmd.name == 'delete': | ||
| dels = spec.get('deletes') | ||
| if dels: | ||
| # FIXME[matt] is there ever more than one here? | ||
| cmd.query = dels[0].get("q") | ||
|
|
||
| return cmd | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,197 @@ | ||
|
|
||
| # stdlib | ||
| import contextlib | ||
| import logging | ||
|
|
||
| # 3p | ||
| from pymongo import MongoClient | ||
| from pymongo.database import Database | ||
| from pymongo.collection import Collection | ||
| from wrapt import ObjectProxy | ||
|
|
||
| # project | ||
| from ...compat import iteritems | ||
| from ...ext import AppTypes | ||
| from ...ext import mongo as mongox | ||
| from ...ext import net as netx | ||
| from .parse import parse_spec, parse_query, Command | ||
|
|
||
|
|
||
| log = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def trace_mongo_client(client, tracer, service=mongox.TYPE): | ||
| tracer.set_service_info( | ||
| service=service, | ||
| app=mongox.TYPE, | ||
| app_type=AppTypes.db, | ||
| ) | ||
| return TracedMongoClient(tracer, service, client) | ||
|
|
||
|
|
||
| class TracedSocket(ObjectProxy): | ||
|
|
||
| _tracer = None | ||
| _srv = None | ||
|
|
||
| def __init__(self, tracer, service, sock): | ||
| super(TracedSocket, self).__init__(sock) | ||
| self._tracer = tracer | ||
| self._srv = service | ||
|
|
||
| def command(self, dbname, spec, *args, **kwargs): | ||
| cmd = None | ||
| try: | ||
| cmd = parse_spec(spec) | ||
| except Exception: | ||
| log.exception("error parsing spec. skipping trace") | ||
|
|
||
| # skip tracing if we don't have a piece of data we need | ||
| if not dbname or not cmd: | ||
| return self.__wrapped__.command(dbname, spec, *args, **kwargs) | ||
|
|
||
| with self.__trace(dbname, cmd) as span: | ||
| return self.__wrapped__.command(dbname, spec, *args, **kwargs) | ||
|
|
||
| def write_command(self, *args, **kwargs): | ||
| # FIXME[matt] parse the db name and collection from the | ||
| # message. | ||
| coll = "" | ||
| db = "" | ||
| cmd = Command("insert_many", coll) | ||
| with self.__trace(db, cmd) as s: | ||
| s.resource = "insert_many" | ||
| result = self.__wrapped__.write_command(*args, **kwargs) | ||
| if result: | ||
| s.set_metric(mongox.ROWS, result.get("n", -1)) | ||
| return result | ||
|
|
||
| def __trace(self, db, cmd): | ||
| s = self._tracer.trace( | ||
| "pymongo.cmd", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think |
||
| span_type=mongox.TYPE, | ||
| service=self._srv, | ||
| ) | ||
|
|
||
| if db: s.set_tag(mongox.DB, db) | ||
| if cmd: | ||
| s.set_tag(mongox.COLLECTION, cmd.coll) | ||
| s.set_tags(cmd.tags) | ||
| # s.set_metrics(cmd.metrics) FIXME[matt] uncomment whe rebase | ||
|
|
||
| s.resource = _resource_from_cmd(cmd) | ||
| if self.address: | ||
| _set_address_tags(s, self.address) | ||
| return s | ||
|
|
||
|
|
||
| class TracedServer(ObjectProxy): | ||
|
|
||
| _tracer = None | ||
| _srv = None | ||
|
|
||
| def __init__(self, tracer, service, topology): | ||
| super(TracedServer, self).__init__(topology) | ||
| self._tracer = tracer | ||
| self._srv = service | ||
|
|
||
| def send_message_with_response(self, operation, *args, **kwargs): | ||
|
|
||
| # if we're processing something unexpected, just skip tracing. | ||
| if getattr(operation, 'name', None) != 'find': | ||
| return self.__wrapped__.send_message_with_response( | ||
| operation, | ||
| *args, | ||
| **kwargs) | ||
|
|
||
| # trace the given query. | ||
| cmd = parse_query(operation) | ||
| with self._tracer.trace( | ||
| "pymongo.cmd", | ||
| span_type=mongox.TYPE, | ||
| service=self._srv) as span: | ||
|
|
||
| span.resource = _resource_from_cmd(cmd) | ||
| span.set_tag(mongox.DB, operation.db) | ||
| span.set_tag(mongox.COLLECTION, cmd.coll) | ||
| span.set_tags(cmd.tags) | ||
|
|
||
| result = self.__wrapped__.send_message_with_response( | ||
| operation, | ||
| *args, | ||
| **kwargs | ||
| ) | ||
|
|
||
| if result and result.address: | ||
| _set_address_tags(span, result.address) | ||
| return result | ||
|
|
||
| @contextlib.contextmanager | ||
| def get_socket(self, *args, **kwargs): | ||
| with self.__wrapped__.get_socket(*args, **kwargs) as s: | ||
| if isinstance(s, TracedSocket): | ||
| yield s | ||
| else: | ||
| yield TracedSocket(self._tracer, self._srv, s) | ||
|
|
||
| class TracedTopology(ObjectProxy): | ||
|
|
||
| _tracer = None | ||
| _srv = None | ||
|
|
||
| def __init__(self, tracer, service, topology): | ||
| super(TracedTopology, self).__init__(topology) | ||
| self._tracer = tracer | ||
| self._srv = service | ||
|
|
||
| def select_server(self, *args, **kwargs): | ||
| s = self.__wrapped__.select_server(*args, **kwargs) | ||
| if isinstance(s, TracedServer): | ||
| return s | ||
| else: | ||
| return TracedServer(self._tracer, self._srv, s) | ||
|
|
||
|
|
||
| class TracedMongoClient(ObjectProxy): | ||
|
|
||
| _tracer = None | ||
| _srv = None | ||
|
|
||
| def __init__(self, tracer, service, client): | ||
| client._topology = TracedTopology(tracer, service, client._topology) | ||
| super(TracedMongoClient, self).__init__(client) | ||
| self._tracer = tracer | ||
| self._srv = service | ||
|
|
||
|
|
||
| def normalize_filter(f=None): | ||
| if f is None: | ||
| return {} | ||
| elif isinstance(f, list): | ||
| # normalize lists of filters | ||
| # e.g. {$or: [ { age: { $lt: 30 } }, { type: 1 } ]} | ||
| return [normalize_filter(s) for s in f] | ||
| else: | ||
| # normalize dicts of filters | ||
| # e.g. {$or: [ { age: { $lt: 30 } }, { type: 1 } ]}) | ||
| out = {} | ||
| for k, v in iteritems(f): | ||
| if isinstance(v, list) or isinstance(v, dict): | ||
| # RECURSION ALERT: needs to move to the agent | ||
| out[k] = normalize_filter(v) | ||
| else: | ||
| out[k] = '?' | ||
| return out | ||
|
|
||
| def _set_address_tags(span, address): | ||
| # the address is only set after the cursor is done. | ||
| if address: | ||
| span.set_tag(netx.TARGET_HOST, address[0]) | ||
| span.set_tag(netx.TARGET_PORT, address[1]) | ||
|
|
||
| def _resource_from_cmd(cmd): | ||
| if cmd.query is not None: | ||
| nq = normalize_filter(cmd.query) | ||
| return "%s %s %s" % (cmd.name, cmd.coll, nq) | ||
| else: | ||
| return "%s %s" % (cmd.name, cmd.coll) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
|
|
||
| TYPE = 'mongodb' | ||
|
|
||
| COLLECTION = 'mongodb.collection' | ||
| DB = 'mongodb.db' | ||
| ROWS = 'mongodb.rows' | ||
| QUERY = 'mongodb.query' | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we just call itnvm looks like that's a pymongo thingcollection? it's not that longThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yea same same