summaryrefslogtreecommitdiffstats
path: root/python/mozperftest/mozperftest/metrics/perfboard/influx.py
blob: 4f7e27072c26ba51e5e9098885ae59582f134dc8 (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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import datetime
import statistics
from collections import defaultdict

from mozperftest import utils
from mozperftest.layers import Layer
from mozperftest.metrics.common import COMMON_ARGS, filtered_metrics
from mozperftest.utils import get_tc_secret, install_package


class Influx(Layer):
    """Sends the metrics to an InfluxDB server"""

    name = "perfboard"
    activated = False
    arguments = COMMON_ARGS
    arguments.update(
        {
            "dashboard": {
                "type": str,
                "default": None,
                "help": "Name of the dashboard - defaults to the script"
                " `component` metadata. When not set, falls back to"
                " `perftest`",
            },
            "influx-host": {
                "type": str,
                "default": "perfboard.dev.mozaws.net",
            },
            "influx-user": {
                "type": str,
                "default": "admin",
            },
            "influx-port": {
                "type": int,
                "default": 8086,
            },
            "influx-password": {
                "type": str,
                "default": None,
            },
            "influx-db": {
                "type": str,
                "default": "perf",
            },
            "grafana-host": {
                "type": str,
                "default": "perfboard.dev.mozaws.net",
            },
            "grafana-key": {
                "type": str,
                "default": None,
            },
            "grafana-port": {
                "type": int,
                "default": 3000,
            },
        }
    )

    def _setup(self):
        venv = self.mach_cmd.virtualenv_manager
        try:
            from influxdb import InfluxDBClient
        except ImportError:
            install_package(venv, "influxdb", ignore_failure=False)
            from influxdb import InfluxDBClient

        try:
            from mozperftest.metrics.perfboard.grafana import Grafana
        except ImportError:
            install_package(venv, "grafana_api", ignore_failure=False)
            from mozperftest.metrics.perfboard.grafana import Grafana

        if utils.ON_TRY:
            secret = get_tc_secret()
            i_host = secret["influx_host"]
            i_port = secret["influx_port"]
            i_user = secret["influx_user"]
            i_password = secret["influx_password"]
            i_dbname = secret["influx_db"]
            g_key = secret["grafana_key"]
            g_host = secret["grafana_host"]
            g_port = secret["grafana_port"]
        else:
            i_host = self.get_arg("influx-host")
            i_port = self.get_arg("influx-port")
            i_user = self.get_arg("influx-user")
            i_password = self.get_arg("influx-password")
            if i_password is None:
                raise Exception("You need to set --perfboard-influx-password")
            i_dbname = self.get_arg("influx-db")
            g_key = self.get_arg("grafana-key")
            if g_key is None:
                raise Exception("You need to set --perfboard-grafana-key")
            g_host = self.get_arg("grafana-host")
            g_port = self.get_arg("grafana-port")

        self.client = InfluxDBClient(i_host, i_port, i_user, i_password, i_dbname)
        # this will error out if the server is unreachable
        self.client.ping()
        self.grafana = Grafana(self, g_key, g_host, g_port)

    def _build_point(self, name, component, values, date):
        value = statistics.mean(values)
        return {
            "measurement": name,
            "tags": {
                "component": component,
            },
            "time": date,
            "fields": {"Float_value": float(value)},
        }

    def run(self, metadata):
        when = datetime.datetime.utcnow()
        date = when.isoformat()
        metrics = self.get_arg("metrics")

        # Get filtered metrics
        results = filtered_metrics(
            metadata,
            self.get_arg("output"),
            self.get_arg("prefix"),
            metrics=metrics,
            transformer=self.get_arg("transformer"),
            split_by=self.get_arg("split-by"),
            simplify_names=self.get_arg("simplify-names"),
            simplify_exclude=self.get_arg("simplify-exclude"),
        )

        if not results:
            self.warning("No results left after filtering")
            return metadata

        # there's one thing we don't do yet is getting a timestamp
        # for each measure that is happening in browsertime or xpcshell
        # if we had it, we could send all 13/25 samples, each one with
        # their timestamp, to InfluxDB, and let Grafana handle the
        # mean() or median() part.
        #
        # Until we have this, here we convert the series to
        # a single value and timestamp
        self._setup()
        component = self.get_arg("dashboard")
        if component is None:
            component = metadata.script.get("component", "perftest")

        data = defaultdict(list)
        for name, res in results.items():
            for line in res:
                if "subtest" not in line:
                    continue
                metric_name = line["subtest"]
                short_name = metric_name.split(".")[-1]
                short_name = short_name.lower()
                if metrics and not any(
                    [m.lower().startswith(short_name.lower()) for m in metrics]
                ):
                    continue
                values = [v["value"] for v in line["data"]]
                data[short_name].extend(values)

        if not data:
            self.warning("No results left after filtering")
            return data

        points = []
        for metric_name, values in data.items():
            try:
                point = self._build_point(metric_name, component, values, date)
            except TypeError:
                continue
            points.append(point)

        self.info("Sending data to InfluxDB")
        self.client.write_points(points)

        # making sure we expose it in Grafana
        test_name = self.get_arg("tests")[0]
        test_name = test_name.split("/")[-1]
        for metric_name in data:
            self.grafana.add_panel(component, test_name, metric_name)

        return metadata