~free.ekanayaka/landscape-client/lucid-1.5.0-0ubuntu0.10.04.0

« back to all changes in this revision

Viewing changes to landscape/sysinfo/sysinfo.py

  • Committer: Bazaar Package Importer
  • Author(s): Rick Clark
  • Date: 2008-09-08 16:35:57 UTC
  • mfrom: (1.1.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20080908163557-l3ixzj5dxz37wnw2
Tags: 1.0.18-0ubuntu1
New upstream release 

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import textwrap
 
2
from logging import getLogger
 
3
import math
 
4
 
 
5
from twisted.python.failure import Failure
 
6
 
 
7
from landscape.lib.twisted_util import gather_results
 
8
from landscape.lib.log import log_failure
 
9
from landscape.plugin import PluginRegistry
 
10
 
 
11
 
 
12
class SysInfoPluginRegistry(PluginRegistry):
 
13
    """
 
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
 
16
    into the registry.
 
17
    
 
18
    There are three kinds of details collected: headers, notes, and footnotes.
 
19
 
 
20
    They are presented to the user in a way similar to the following:
 
21
 
 
22
        Header1: Value1   Header3: Value3
 
23
        Header2: Value2   Header4: Value4
 
24
 
 
25
        => This is first note
 
26
        => This is the second note
 
27
 
 
28
        The first footnote.
 
29
        The second footnote.
 
30
 
 
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.
 
36
    """
 
37
 
 
38
    def __init__(self):
 
39
        super(SysInfoPluginRegistry, self).__init__()
 
40
        self._header_index = {}
 
41
        self._headers = []
 
42
        self._notes = []
 
43
        self._footnotes = []
 
44
        self._plugin_error = False
 
45
 
 
46
    def add_header(self, name, value):
 
47
        """Add a new information header to be displayed to the user.
 
48
 
 
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.
 
52
 
 
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.
 
57
        """
 
58
        index = self._header_index.get(name)
 
59
        if index is None:
 
60
            self._header_index[name] = len(self._headers)
 
61
            self._headers.append((name, value))
 
62
        else:
 
63
            self._headers[index] = (name, value)
 
64
 
 
65
    def get_headers(self):
 
66
        """Get all information headers to be displayed to the user.
 
67
 
 
68
        Headers which were added with value None are not included in
 
69
        the result.
 
70
        """
 
71
        return [pair for pair in self._headers if pair[1] is not None]
 
72
 
 
73
    def add_note(self, note):
 
74
        """Add a new eventual note to be shown up to the administrator."""
 
75
        self._notes.append(note)
 
76
 
 
77
    def get_notes(self):
 
78
        """Get all eventual notes to be shown up to the administrator."""
 
79
        return self._notes
 
80
 
 
81
    def add_footnote(self, note):
 
82
        """Add a new footnote to be shown up to the administrator."""
 
83
        self._footnotes.append(note)
 
84
 
 
85
    def get_footnotes(self):
 
86
        """Get all footnotes to be shown up to the administrator."""
 
87
        return self._footnotes
 
88
 
 
89
    def run(self):
 
90
        """Run all plugins, and return a deferred aggregating their results.
 
91
 
 
92
        This will call the run() method on each of the registered plugins,
 
93
        and return a deferred which aggregates each resulting deferred.
 
94
        """
 
95
        deferreds = []
 
96
        for plugin in self.get_plugins():
 
97
            try:
 
98
                result = plugin.run()
 
99
            except:
 
100
                self._log_plugin_error(Failure(), plugin)
 
101
            else:
 
102
                result.addErrback(self._log_plugin_error, plugin)
 
103
                deferreds.append(result)
 
104
        return gather_results(deferreds).addCallback(self._report_error_note)
 
105
 
 
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)
 
111
 
 
112
    def _report_error_note(self, result):
 
113
        if self._plugin_error:
 
114
            self.add_note(
 
115
                "There were exceptions while processing one or more plugins. "
 
116
                "See ~/.landscape/sysinfo.log for more information.")
 
117
        return result
 
118
 
 
119
 
 
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.
 
123
 
 
124
    This function will format headers notes and footnotes in a way that
 
125
    looks similar to the following:
 
126
 
 
127
        Header1: Value1   Header3: Value3
 
128
        Header2: Value2   Header4: Value4
 
129
 
 
130
        => This is first note
 
131
        => This is the second note
 
132
 
 
133
        The first footnote.
 
134
        The second footnote.
 
135
 
 
136
    Header columns will be dynamically adjusted to conform to the size
 
137
    of header labels and values.
 
138
    """
 
139
 
 
140
    # Indentation spacing is easier to handle if we just take it off the width.
 
141
    width -= len(indent)
 
142
 
 
143
    headers_len = len(headers)
 
144
    value_separator = ": "
 
145
 
 
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.
 
149
    min_length = width
 
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))))
 
154
 
 
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
 
159
    # of columns.
 
160
    while True:
 
161
        # Check if the current number of columns would fit in the screen.
 
162
        # Note that headers are indented like this:
 
163
        #
 
164
        #     Header:         First value
 
165
        #     Another header: Value
 
166
        #
 
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)))
 
170
        header_lengths = []
 
171
        total_length = 0
 
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
 
175
            # outputing it.
 
176
            widest_header_len = 0
 
177
            widest_value_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))
 
186
 
 
187
            if column > 0:
 
188
                # Account for the spacing between each column.
 
189
                total_length += len(column_separator)
 
190
 
 
191
            total_length += (widest_header_len + widest_value_len +
 
192
                             len(value_separator))
 
193
 
 
194
            # Keep track of these lengths for building the output later.
 
195
            header_lengths.append((widest_header_len, widest_value_len))
 
196
 
 
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.
 
200
            break
 
201
 
 
202
        # Otherwise, do the whole thing again with one less column.
 
203
        columns -= 1
 
204
 
 
205
 
 
206
    # Alright! Show time! Let's build the headers line by line.
 
207
    lines = []
 
208
    for row in range(headers_per_column):
 
209
        line = indent
 
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]
 
222
                if column > 0:
 
223
                    # Add inter-column spacing.
 
224
                    line += column_separator
 
225
                # And append the column to the current line.
 
226
                line += (header +
 
227
                         value_separator +
 
228
                         " " * (widest_header_len - len(header)) +
 
229
                         value)
 
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))
 
234
        lines.append(line)
 
235
 
 
236
    if notes:
 
237
        if lines:
 
238
            # Some spacing between headers and notes.
 
239
            lines.append("")
 
240
        initial_indent = indent + note_prefix
 
241
        for note in notes:
 
242
            lines.extend(
 
243
                textwrap.wrap(note,
 
244
                              initial_indent=initial_indent,
 
245
                              subsequent_indent=" "*len(initial_indent),
 
246
                              width=width))
 
247
 
 
248
    if footnotes:
 
249
        if lines:
 
250
            lines.append("")
 
251
        lines.extend(indent + footnote for footnote in footnotes)
 
252
 
 
253
    return "\n".join(lines)