~viswesn/juju-ci-tools/ensure_provider_cleanup

« back to all changes in this revision

Viewing changes to pprof_collector.py

  • Committer: viswesn
  • Date: 2017-03-16 11:05:43 UTC
  • mfrom: (1823.1.112 trunk)
  • Revision ID: viswesn@gmail.com-20170316110543-ay89t3dcl1cknp5o
Merge to trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""Collector of pprof details using juju introspection."""
 
2
 
 
3
from __future__ import print_function
 
4
 
 
5
from datetime import datetime
 
6
import logging
 
7
import os
 
8
import requests
 
9
 
 
10
from utility import get_unit_ipaddress
 
11
 
 
12
__metaclass__ = type
 
13
 
 
14
log = logging.getLogger("pprof_collector")
 
15
 
 
16
 
 
17
class NoopCollector:
 
18
 
 
19
    def __init__(self, client, machine_id):
 
20
        """Internal collector for pprof profiles, not intended to be used
 
21
        directly
 
22
 
 
23
        Does not actually install or collect data. Used as a stand in when
 
24
        collection is disabled.
 
25
 
 
26
        :param client: jujupy.ModelClient object to install the pprof software
 
27
          with.
 
28
        :machine_id: ID of the juju machine with which to install pprof
 
29
          software/charm.
 
30
        """
 
31
        self.client = client
 
32
        self.machine_id = machine_id
 
33
 
 
34
    def collect_profile(self, filepath, seconds):
 
35
        """Collect `seconds` worth of CPU profile."""
 
36
        pass
 
37
 
 
38
    def collect_heap(self, filepath, seconds):
 
39
        """Collect `seconds` worth of heap profile."""
 
40
        pass
 
41
 
 
42
    def collect_goroutines(self, filepath, seconds):
 
43
        """Collect `seconds` worth of goroutines profile."""
 
44
        pass
 
45
 
 
46
 
 
47
class ActiveCollector(NoopCollector):
 
48
 
 
49
    def __init__(self, client, machine_id):
 
50
        """Internal collector for pprof profiles, not intended to be used
 
51
        directly.
 
52
 
 
53
        Installs required  on `machine_id` and enable collection of
 
54
        pprof details.
 
55
 
 
56
        For instance to install on the controller machine pass in
 
57
        client=client.get_contoller_client() and machine_id='0'
 
58
 
 
59
        :param client: jujupy.ModelClient object to install the pprof software
 
60
          with.
 
61
        :machine_id: ID of the juju machine with which to install pprof
 
62
          software/charm.
 
63
        """
 
64
        self.machine_id = machine_id
 
65
        self.introspection_ip = install_introspection_charm(
 
66
            client, machine_id)
 
67
 
 
68
    def _collect_profile(self, profile, filepath, seconds):
 
69
        profile_url = get_profile_url(
 
70
            self.introspection_ip, self.machine_id, profile, seconds)
 
71
        get_profile_reading(profile_url, filepath)
 
72
 
 
73
    def collect_profile(self, filepath, seconds):
 
74
        """Collect `seconds` worth of CPU profile."""
 
75
        log.info('Collecting CPU profile data.')
 
76
        self._collect_profile('profile', filepath, seconds)
 
77
 
 
78
    def collect_heap(self, filepath, seconds):
 
79
        """Collect `seconds` worth of heap profile."""
 
80
        log.info('Collecting heap profile data.')
 
81
        self._collect_profile('heap', filepath, seconds)
 
82
 
 
83
    def collect_goroutines(self, filepath, seconds):
 
84
        """Collect `seconds` worth of goroutines profile."""
 
85
        log.info('Collecting goroutines profile data.')
 
86
        self._collect_profile('goroutines', filepath, seconds)
 
87
 
 
88
 
 
89
def get_profile_url(ipaddress, machine_id, profile_name, seconds):
 
90
    profile_url = 'agents/machine-{}/debug/pprof/{}'.format(
 
91
        machine_id, profile_name)
 
92
    return 'http://{}:19090/{}?seconds={}'.format(
 
93
        ipaddress, profile_url, seconds)
 
94
 
 
95
 
 
96
def get_profile_reading(url, filepath):
 
97
    res = requests.get(url)
 
98
    with open(filepath, 'wb') as f:
 
99
        f.write(res.content)
 
100
 
 
101
 
 
102
def install_introspection_charm(client, machine_id):
 
103
    client.deploy(_get_introspection_charm_url(), to=machine_id)
 
104
    client.wait_for_started()
 
105
    client.wait_for_workloads()
 
106
    client.juju('expose', 'juju-introspection')
 
107
 
 
108
    return get_unit_ipaddress(client, 'juju-introspection/0')
 
109
 
 
110
 
 
111
def _get_introspection_charm_url():
 
112
    return 'cs:~axwalk/juju-introspection'
 
113
 
 
114
 
 
115
class PPROFCollector:
 
116
 
 
117
    FILE_TIMESTAMP = '%y%m%d-%H%M%S'
 
118
 
 
119
    def __init__(self, client, machine_ids, logs_dir, active=False):
 
120
        """Collector of pprof profiles from a machine.
 
121
 
 
122
        Defaults to being non-active meaning that any attempt to collect a
 
123
        profile will be a no-op.
 
124
        Setting active (either at creation or at any point during the lifespan
 
125
        of this object) will enable collection of pprof profiles from the
 
126
        `machine_id`.
 
127
        (Note. first time going active will result in the introspection charm
 
128
        being deployed to the `machine_id.)
 
129
 
 
130
        :param client: ModelClient to use to communicate with machine_ids.
 
131
        :param machine_ids: List of machine IDs to have collections for.
 
132
        :param logs_dir: Directory in which to store profile data.
 
133
        :param active: Bool indicating wherever to enable collection of data or
 
134
          not.
 
135
 
 
136
        """
 
137
        if not isinstance(machine_ids, list):
 
138
            raise ValueError('List of machine IDs required.')
 
139
 
 
140
        self._active_collectors = []
 
141
        self._noop_collectors = []
 
142
        self._active = active
 
143
 
 
144
        self._cpu_profile_path = os.path.join(logs_dir, 'cpu_profile')
 
145
        os.makedirs(self._cpu_profile_path)
 
146
        self._heap_profile_path = os.path.join(logs_dir, 'heap_profile')
 
147
        os.makedirs(self._heap_profile_path)
 
148
        self._goroutines_path = os.path.join(logs_dir, 'goroutines_profile')
 
149
        os.makedirs(self._goroutines_path)
 
150
 
 
151
        # Store in case we need to activate a collector at a later date.
 
152
        self._client = client
 
153
        self._machine_ids = machine_ids
 
154
 
 
155
        if self._active:
 
156
            self.set_active()
 
157
        else:
 
158
            self.unset_active()
 
159
 
 
160
    def set_active(self):
 
161
        log.info('Setting PPROF collection to ACTIVE.')
 
162
        if not self._active_collectors:
 
163
            for m_id in self._machine_ids:
 
164
                self._active_collectors.append(
 
165
                    ActiveCollector(self._client, m_id))
 
166
        self._collectors = self._active_collectors
 
167
        self._active = True
 
168
 
 
169
    def unset_active(self):
 
170
        log.info('Setting PPROF collection to INACTIVE.')
 
171
        if not self._noop_collectors:
 
172
            for m_id in self._machine_ids:
 
173
                self._noop_collectors.append(
 
174
                    NoopCollector(self._client, m_id))
 
175
        self._collectors = self._noop_collectors
 
176
        self._active = False
 
177
 
 
178
    def _get_profile_file_path(self, dir_path, machine_id):
 
179
        """Given a directory create a timestamped file path."""
 
180
        ts_file = datetime.utcnow().strftime(self.FILE_TIMESTAMP)
 
181
        return os.path.join(
 
182
            dir_path,
 
183
            'machine-{}-{}.pprof'.format(
 
184
                machine_id,
 
185
                ts_file))
 
186
 
 
187
    def collect_profile(self, seconds=5):
 
188
        """Collect `seconds` worth of CPU profile."""
 
189
        for collector in self._collectors:
 
190
            collector.collect_profile(
 
191
                self._get_profile_file_path(
 
192
                    self._cpu_profile_path,
 
193
                    collector.machine_id,
 
194
                ),
 
195
                seconds
 
196
            )
 
197
 
 
198
    def collect_heap(self, seconds=5):
 
199
        """Collect `seconds` worth of heap profile."""
 
200
        for collector in self._collectors:
 
201
            collector.collect_heap(
 
202
                self._get_profile_file_path(
 
203
                    self._heap_profile_path,
 
204
                    collector.machine_id,
 
205
                ),
 
206
                seconds
 
207
            )
 
208
 
 
209
    def collect_goroutines(self, seconds=5):
 
210
        """Collect `seconds` worth of goroutines profile."""
 
211
        for collector in self._collectors:
 
212
            collector.collect_goroutines(
 
213
                self._get_profile_file_path(
 
214
                    self._goroutines_path,
 
215
                    collector.machine_id,
 
216
                ),
 
217
                seconds
 
218
            )