1
"""Collector of pprof details using juju introspection."""
3
from __future__ import print_function
5
from datetime import datetime
10
from utility import get_unit_ipaddress
14
log = logging.getLogger("pprof_collector")
19
def __init__(self, client, machine_id):
20
"""Internal collector for pprof profiles, not intended to be used
23
Does not actually install or collect data. Used as a stand in when
24
collection is disabled.
26
:param client: jujupy.ModelClient object to install the pprof software
28
:machine_id: ID of the juju machine with which to install pprof
32
self.machine_id = machine_id
34
def collect_profile(self, filepath, seconds):
35
"""Collect `seconds` worth of CPU profile."""
38
def collect_heap(self, filepath, seconds):
39
"""Collect `seconds` worth of heap profile."""
42
def collect_goroutines(self, filepath, seconds):
43
"""Collect `seconds` worth of goroutines profile."""
47
class ActiveCollector(NoopCollector):
49
def __init__(self, client, machine_id):
50
"""Internal collector for pprof profiles, not intended to be used
53
Installs required on `machine_id` and enable collection of
56
For instance to install on the controller machine pass in
57
client=client.get_contoller_client() and machine_id='0'
59
:param client: jujupy.ModelClient object to install the pprof software
61
:machine_id: ID of the juju machine with which to install pprof
64
self.machine_id = machine_id
65
self.introspection_ip = install_introspection_charm(
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)
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)
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)
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)
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)
96
def get_profile_reading(url, filepath):
97
res = requests.get(url)
98
with open(filepath, 'wb') as f:
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')
108
return get_unit_ipaddress(client, 'juju-introspection/0')
111
def _get_introspection_charm_url():
112
return 'cs:~axwalk/juju-introspection'
115
class PPROFCollector:
117
FILE_TIMESTAMP = '%y%m%d-%H%M%S'
119
def __init__(self, client, machine_ids, logs_dir, active=False):
120
"""Collector of pprof profiles from a machine.
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
127
(Note. first time going active will result in the introspection charm
128
being deployed to the `machine_id.)
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
137
if not isinstance(machine_ids, list):
138
raise ValueError('List of machine IDs required.')
140
self._active_collectors = []
141
self._noop_collectors = []
142
self._active = active
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)
151
# Store in case we need to activate a collector at a later date.
152
self._client = client
153
self._machine_ids = machine_ids
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
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
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)
183
'machine-{}-{}.pprof'.format(
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,
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,
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,