2
from logging import getLogger
5
from twisted.python.failure import Failure
7
from landscape.lib.twisted_util import gather_results
8
from landscape.lib.log import log_failure
9
from landscape.plugin import PluginRegistry
12
class SysInfoPluginRegistry(PluginRegistry):
14
When the sysinfo plugin registry is run, it will run each of the
15
registered plugins so that they get a chance to feed information
18
There are three kinds of details collected: headers, notes, and footnotes.
20
They are presented to the user in a way similar to the following:
22
Header1: Value1 Header3: Value3
23
Header2: Value2 Header4: Value4
26
=> This is the second note
31
Headers are supposed to display information which is regularly
32
available, such as the load and temperature of the system. Notes
33
contain eventual information, such as warnings of high temperatures,
34
and low disk space. Finally, footnotes contain pointers to further
35
information such as URLs.
39
super(SysInfoPluginRegistry, self).__init__()
40
self._header_index = {}
44
self._plugin_error = False
46
def add_header(self, name, value):
47
"""Add a new information header to be displayed to the user.
49
Each header name is only present once. If a header is added
50
multiple times, the last value added will be returned in
51
the get_headers() call.
53
Headers with value None are not returned by get_headers(), but
54
they still allocate a position in the list. This fact may be
55
explored to create a deterministic ordering even when dealing
56
with values obtained asynchornously.
58
index = self._header_index.get(name)
60
self._header_index[name] = len(self._headers)
61
self._headers.append((name, value))
63
self._headers[index] = (name, value)
65
def get_headers(self):
66
"""Get all information headers to be displayed to the user.
68
Headers which were added with value None are not included in
71
return [pair for pair in self._headers if pair[1] is not None]
73
def add_note(self, note):
74
"""Add a new eventual note to be shown up to the administrator."""
75
self._notes.append(note)
78
"""Get all eventual notes to be shown up to the administrator."""
81
def add_footnote(self, note):
82
"""Add a new footnote to be shown up to the administrator."""
83
self._footnotes.append(note)
85
def get_footnotes(self):
86
"""Get all footnotes to be shown up to the administrator."""
87
return self._footnotes
90
"""Run all plugins, and return a deferred aggregating their results.
92
This will call the run() method on each of the registered plugins,
93
and return a deferred which aggregates each resulting deferred.
96
for plugin in self.get_plugins():
100
self._log_plugin_error(Failure(), plugin)
102
result.addErrback(self._log_plugin_error, plugin)
103
deferreds.append(result)
104
return gather_results(deferreds).addCallback(self._report_error_note)
106
def _log_plugin_error(self, failure, plugin):
107
self._plugin_error = True
108
message = "%s plugin raised an exception." % plugin.__class__.__name__
109
logger = getLogger("landscape-sysinfo")
110
log_failure(failure, message, logger=logger)
112
def _report_error_note(self, result):
113
if self._plugin_error:
115
"There were exceptions while processing one or more plugins. "
116
"See ~/.landscape/sysinfo.log for more information.")
120
def format_sysinfo(headers=(), notes=(), footnotes=(), width=80, indent="",
121
column_separator=" ", note_prefix="=> "):
122
"""Format sysinfo headers, notes and footnotes to be displayed.
124
This function will format headers notes and footnotes in a way that
125
looks similar to the following:
127
Header1: Value1 Header3: Value3
128
Header2: Value2 Header4: Value4
130
=> This is first note
131
=> This is the second note
136
Header columns will be dynamically adjusted to conform to the size
137
of header labels and values.
140
# Indentation spacing is easier to handle if we just take it off the width.
143
headers_len = len(headers)
144
value_separator = ": "
146
# Compute the number of columns in the header. To do that, we first
147
# do a rough estimative of the maximum number of columns feasible,
148
# and then we go back from there until we can fit things.
150
for header, value in headers:
151
min_length = min(min_length, len(header)+len(value)+2) # 2 for ": "
152
columns = int(math.ceil(float(width) /
153
(min_length + len(column_separator))))
155
# Okay, we've got a base for the number of columns. Now, since
156
# columns may have different lengths, and the length of each column
157
# will change as we compress headers in less and less columns, we
158
# have to perform some backtracking to compute a good feasible number
161
# Check if the current number of columns would fit in the screen.
162
# Note that headers are indented like this:
164
# Header: First value
165
# Another header: Value
167
# So the column length is the sum of the widest header, plus the
168
# widest value, plus the value separator.
169
headers_per_column = int(math.ceil(headers_len / float(columns)))
172
for column in range(columns):
173
# We must find the widest header and value, both to compute the
174
# column length, and also to compute per-column padding when
176
widest_header_len = 0
178
for row in range(headers_per_column):
179
header_index = column * headers_per_column + row
180
# There are potentially less headers in the last column,
181
# so let's watch out for these here.
182
if header_index < headers_len:
183
header, value = headers[header_index]
184
widest_header_len = max(widest_header_len, len(header))
185
widest_value_len = max(widest_value_len, len(value))
188
# Account for the spacing between each column.
189
total_length += len(column_separator)
191
total_length += (widest_header_len + widest_value_len +
192
len(value_separator))
194
# Keep track of these lengths for building the output later.
195
header_lengths.append((widest_header_len, widest_value_len))
197
if columns == 1 or total_length < width:
198
# If there's just one column, or if we're within the requested
199
# length, we're good to go.
202
# Otherwise, do the whole thing again with one less column.
206
# Alright! Show time! Let's build the headers line by line.
208
for row in range(headers_per_column):
210
# Pick all columns for this line. Note that this means that
211
# for 4 headers with 2 columns, we pick header 0 and 2 for
212
# the first line, since we show headers 0 and 1 in the first
213
# column, and headers 2 and 3 in the second one.
214
for column in range(columns):
215
header_index = column * headers_per_column + row
216
# There are potentially less headers in the last column, so
217
# let's watch out for these here.
218
if header_index < headers_len:
219
header, value = headers[header_index]
220
# Get the widest header/value on this column, for padding.
221
widest_header_len, widest_value_len = header_lengths[column]
223
# Add inter-column spacing.
224
line += column_separator
225
# And append the column to the current line.
228
" " * (widest_header_len - len(header)) +
230
# If there are more columns in this line, pad it up so
231
# that the next column's header is correctly aligned.
232
if headers_len > (column+1) * headers_per_column + row:
233
line += " " * (widest_value_len - len(value))
238
# Some spacing between headers and notes.
240
initial_indent = indent + note_prefix
244
initial_indent=initial_indent,
245
subsequent_indent=" "*len(initial_indent),
251
lines.extend(indent + footnote for footnote in footnotes)
253
return "\n".join(lines)