summaryrefslogtreecommitdiff
path: root/osprofiler/drivers/mongodb.py
blob: fdd4a46984055dcf6fa5e319f28ad37dc28a8916 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# Copyright 2016 Mirantis Inc.
# All Rights Reserved.
#
#    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 osprofiler.drivers import base
from osprofiler import exc


class MongoDB(base.Driver):
    def __init__(self, connection_str, db_name="osprofiler", project=None,
                 service=None, host=None, **kwargs):
        """MongoDB driver for OSProfiler."""

        super(MongoDB, self).__init__(connection_str, project=project,
                                      service=service, host=host, **kwargs)
        try:
            from pymongo import MongoClient
        except ImportError:
            raise exc.CommandError(
                "To use OSProfiler with MongoDB driver, "
                "please install `pymongo` library. "
                "To install with pip:\n `pip install pymongo`.")

        client = MongoClient(self.connection_str, connect=False)
        self.db = client[db_name]

    @classmethod
    def get_name(cls):
        return "mongodb"

    def notify(self, info):
        """Send notifications to MongoDB.

        :param info:  Contains information about trace element.
                      In payload dict there are always 3 ids:
                      "base_id" - uuid that is common for all notifications
                      related to one trace. Used to simplify retrieving of all
                      trace elements from MongoDB.
                      "parent_id" - uuid of parent element in trace
                      "trace_id" - uuid of current element in trace
                      With parent_id and trace_id it's quite simple to build
                      tree of trace elements, which simplify analyze of trace.
        """
        data = info.copy()
        data["project"] = self.project
        data["service"] = self.service
        self.db.profiler.insert_one(data)

        if (self.filter_error_trace
                and data.get("info", {}).get("etype") is not None):
            self.notify_error_trace(data)

    def notify_error_trace(self, data):
        """Store base_id and timestamp of error trace to a separate db."""
        self.db.profiler_error.update(
            {"base_id": data["base_id"]},
            {"base_id": data["base_id"], "timestamp": data["timestamp"]},
            upsert=True
        )

    def list_traces(self, fields=None):
        """Query all traces from the storage.

        :param fields: Set of trace fields to return. Defaults to 'base_id'
                       and 'timestamp'
        :returns: List of traces, where each trace is a dictionary containing
                  at least `base_id` and `timestamp`.
        """
        fields = set(fields or self.default_trace_fields)
        ids = self.db.profiler.find({}).distinct("base_id")
        out_format = {"base_id": 1, "timestamp": 1, "_id": 0}
        out_format.update({i: 1 for i in fields})
        return [self.db.profiler.find(
                {"base_id": i}, out_format).sort("timestamp")[0] for i in ids]

    def list_error_traces(self):
        """Returns all traces that have error/exception."""
        out_format = {"base_id": 1, "timestamp": 1, "_id": 0}
        return self.db.profiler_error.find({}, out_format)

    def get_report(self, base_id):
        """Retrieves and parses notification from MongoDB.

        :param base_id: Base id of trace elements.
        """
        for n in self.db.profiler.find({"base_id": base_id}, {"_id": 0}):
            trace_id = n["trace_id"]
            parent_id = n["parent_id"]
            name = n["name"]
            project = n["project"]
            service = n["service"]
            host = n["info"]["host"]
            timestamp = n["timestamp"]

            self._append_results(trace_id, parent_id, name, project, service,
                                 host, timestamp, n)

        return self._parse_results()