1#!/usr/bin/env python3
2
3"""Command line event and event class viewer, and ingester.
4
5Requests that include sid, start or stop switch to event retrieval mode.
6Otherwise, they should switch to event class display mode.
7"""
8
9import sys
10import logging
11from datetime import timedelta
12
13import django
14from lxml.etree import Element
15
16from chart.db.connection import db_connect
17from chart.project import settings
18from chart.common.args import ArgumentParser
19from chart.common.prettyprint import Table
20from chart.events.eventclass import EventClass
21from chart.events.db import find_events
22from chart.events.db import find_single_event
23from chart.common.xml import XMLElement
24from chart.events.emails import email_event
25
26db_conn = db_connect('EVENTS')
27
28logger = logging.getLogger()
29
30NEWLINE = '\n'
31
32
33def show_all_classes(target=sys.stdout):
34 """List all defined event classes."""
35 for e in EventClass.all():
36 target.write(e.name + NEWLINE)
37
38
39def show_single_event(event_id, target=sys.stdout):
40 """Display information about a single event."""
41 # initialise django templaing
42 django.setup()
43
44 e = find_single_event(event_id=event_id)
45 if e is None:
46 raise ValueError('Event {id} not found'.format(id=event_id))
47
48 t = Table('Common properties')
49 t.append(('ID', e.event_id))
50 t.append(('Class', e.event_classname))
51 t.append(('SID', e.sid.name))
52 t.append(('Gen_method', e.gen_method))
53 t.append(('Gen_time', e.gen_time))
54 t.append(('Start_time', e.start_time))
55 t.append(('Stop_time', e.stop_time))
56 t.write(target)
57
58 t = Table('Instance properties')
59 for k, v in e.event_class.instance_properties.items():
60 desc = [v['type']]
61 if 'optional' in v:
62 desc.append('optional')
63
64 value = e.instance_properties.get(k)
65 if isinstance(value, dict):
66 # quick hack for json property lists - should use instance property type instead
67 value = str(value)
68
69 t.append((k, ','.join(desc), '<not set>' if value is None else value))
70
71 t.write(target)
72
73 t = Table(title='Class properties')
74 for k, v in e.event_class.class_properties.items():
75 t.append((k, v))
76
77 t.write(target)
78
79 target.write('Description:\n\n')
80 target.write(e.description() + NEWLINE)
81
82
83def delete_single_event(event_id):
84 """Delete a single event identified by event id."""
85 db_conn.query('DELETE FROM events WHERE id=:id', id=event_id)
86 db_conn.commit()
87 logger.info('Deleted {id}'.format(id=event_id))
88
89
90def delete_from_stdin():
91 """Read lines from stdin, each containing an event ID to be
92 deleted."""
93 cur = db_conn.prepared_cursor('DELETE FROM events WHERE id=:id')
94 cc = 0
95 for event_id in sys.stdin.readlines():
96 event_id = int(event_id)
97 logger.info('Deleting {id}'.format(id=event_id))
98 cur.execute(None, id=event_id)
99 cc += 1
100
101 db_conn.commit()
102 logger.info('Deleted {cc} events'.format(cc=cc))
103
104
105def daily_events(sid, start_time, stop_time, event_classes):
106 """Read counts of events per day."""
107 classnames = [e.name for e in event_classes]
108 t = Table(title='Daily event counts for {sid}'.format(sid=sid),
109 headings=['Data'] + classnames)
110 acc = start_time
111 DAY = timedelta(days=1)
112 while acc < stop_time:
113 cells = [0] * len(event_classes)
114 for e in find_events(sid=sid,
115 start_time=acc,
116 stop_time=acc + DAY,
117 event_class=None if len(event_classes) == 0 else list(event_classes)):
118 pos = classnames.index(e.event_classname)
119 cells[pos] += 1
120
121 t.append([acc.date()] + cells)
122 acc += DAY
123
124 t.write()
125
126
127def list_events(sid,
128 start_time,
129 stop_time,
130 event_classes,
131 min_duration,
132 max_duration,
133 xml,
134 csv,
135 duplicates,
136 datefmt,
137 description):
138 """Show a table / CSV file / XML file of matching events."""
139 events = list(find_events(sid=sid,
140 start_time=start_time,
141 stop_time=stop_time,
142 event_class=None if len(event_classes) == 0 else list(event_classes),
143 min_duration=min_duration,
144 max_duration=max_duration))
145
146 if xml:
147 logger.info('Writing matching events to {path}'.format(path=xml))
148 root = XMLElement('events')
149
150 for e in events:
151 e.to_xml(root.elem)
152
153 root.write(xml)
154 return
155
156 if csv:
157 t = Table(headings=('Id', 'Class', 'Start', 'Stop', 'SID', 'Parameters'))
158
159 else:
160 t = Table(title='Events in range {start} to {stop}'.format(start=start_time,
161 stop=stop_time),
162 headings=('Id', 'Class', 'Start', 'Stop', 'SID', 'Parameters'))
163
164 last_e = None
165
166 for e in events:
167 show_id = e.event_id
168 if duplicates and last_e is not None:
169 if (e.start_time == last_e.start_time and
170 e.stop_time == last_e.stop_time and
171 e.event_classname == last_e.event_classname and
172 e.sid == last_e.sid):
173
174 show_id = str(show_id) + ' *'
175
176 last_e = e
177
178 if datefmt is None:
179 start = e.start_time
180
181 else:
182 start = e.start_time.strftime(datefmt)
183
184 if datefmt is None:
185 stop = e.stop_time
186
187 else:
188 stop = e.stop_time.strftime(datefmt)
189
190 t.append((show_id,
191 e.event_classname,
192 start,
193 stop,
194 e.sid.name,
195 e.description() if description else
196 ','.join(k + '=' + str(v) for k, v in e.instance_properties.items())))
197
198 last_e = e
199
200 if csv:
201 t.write_csv()
202
203 else:
204 t.write()
205
206
207def show_class_info(eventclass, target=sys.stdout):
208 """Display information about a class definition."""
209 ec = EventClass(eventclass)
210 target.write('Definition of class ' + ec.name + NEWLINE)
211 target.write('Description:\n')
212 target.write(ec.description + NEWLINE)
213 target.write('Template:\n')
214 target.write(ec.template + NEWLINE)
215 target.write('Instance properties:\n')
216 for k, v in ec.instance_properties.items():
217 target.write(' ' + k + ': ' + str(v) + NEWLINE)
218 target.write('Raised by: ' + ', '.join(a.name for a in ec.raised_by()) + NEWLINE)
219
220
221def write_xml(event, output):
222 logger.info('Writing matching events to {path}'.format(path=output))
223 root = XMLElement('events')
224 event.to_xml(root.elem)
225 root.write(output)
226
227
228def write_html(event, target=sys.stdout):
229 """Write a single event as an HTML table to `target`."""
230 target.write(event.as_table().to_html_str())
231
232
233def main():
234 """Command line entry point."""
235 parser = ArgumentParser(__doc__)
236
237 setup = parser.add_argument_group('Setup')
238 setup.add_argument('--db',
239 metavar='CONNECTION',
240 help='Use database connection CONNECTION')
241
242 time_selection = parser.add_argument_group('Time range')
243 time_selection.add_argument('--start',
244 type=ArgumentParser.start_time,
245 metavar='TIME',
246 help='Begin search at TIME.')
247 time_selection.add_argument('--stop',
248 type=ArgumentParser.stop_time,
249 metavar='TIME',
250 help='End search at TIME. Default is current system time')
251
252 filtering = parser.add_argument_group('Filtering')
253 filtering.add_argument('--id', '--show', '-i',
254 metavar='ID',
255 type=int,
256 help='Just display event ID')
257 filtering.add_argument('--class', '--classname', '--cls', '--event',
258 metavar='CLASS',
259 dest='event_class',
260 nargs='*',
261 help=('Just search for events of specified classes, including Unix '
262 'style wildcards'))
263 filtering.add_argument('--sid', '-s',
264 metavar='SID',
265 type=ArgumentParser.sid,
266 help=('Restrict search to events where spacecraft-id is specified and '
267 'equal to source'))
268 filtering.add_argument('--min-duration',
269 type=ArgumentParser.timedelta,
270 metavar='MIN',
271 help='Discard any events with no duration or duration less than MIN')
272 filtering.add_argument('--max-duration',
273 type=ArgumentParser.timedelta,
274 metavar='MAX',
275 help='Discard any events with no duration or duration more than MAX')
276
277 display_options = parser.add_argument_group('Display')
278 display_options.add_argument('--desc',
279 action='store_true',
280 help='Show event description instead of instance parameter list')
281 display_options.add_argument('--duplicates',
282 action='store_true',
283 help='Highlight duplicate events')
284 display_options.add_argument('--csv',
285 action='store_true',
286 help='Output in CSV format')
287 display_options.add_argument('--xml',
288 metavar='FILE',
289 help='Output events in XML format to FILE')
290 display_options.add_argument('--datefmt',
291 help='Date format to use')
292 display_options.add_argument('--daily',
293 action='store_true',
294 help='Show daily counts')
295 display_options.add_argument('--html',
296 action='store_true',
297 help='Write as HTML')
298 other_actions = parser.add_argument_group('Other actions')
299 other_actions.add_argument('--remove',
300 action='store_true',
301 help='Delete event referenced by --id')
302 other_actions.add_argument('--list', '-l',
303 action='store_true',
304 help='List all event class names')
305 other_actions.add_argument('--class-info',
306 help='Show description of an event class')
307 other_actions.add_argument('--email',
308 metavar='ADDRESS',
309 help='Send event email to ADDRESS (EUM recepients only)')
310 parser.add_argument_group(other_actions)
311
312 args = parser.parse_args()
313
314 if args.class_info:
315 show_class_info(args.class_info)
316 parser.exit()
317
318 if args.db:
319 settings.set_db_name(args.db)
320
321 if args.list:
322 show_all_classes()
323 parser.exit()
324
325 if args.id:
326 if args.email:
327 email_event(first_name=args.email,
328 last_name='',
329 email=args.email,
330 event=find_single_event(event_id=args.id))
331 parser.exit()
332
333 if args.xml:
334 event = find_single_event(event_id=args.id)
335 write_xml(event, args.xml)
336 parser.exit()
337
338 if args.html:
339 event = find_single_event(event_id=args.id)
340 write_html(event)
341 parser.exit()
342
343 if args.remove:
344 if args.id == '-':
345 delete_from_stdin()
346
347 else:
348 delete_single_event(args.id)
349
350 else:
351 show_single_event(args.id)
352
353 parser.exit()
354
355 elif args.email:
356 parser.error('The --email option only works for sending single events using --id')
357
358 if args.event_class:
359 event_classes = EventClass.expand(args.event_class)
360
361 else:
362 event_classes = set()
363
364 if args.daily:
365 daily_events(sid=args.sid,
366 start_time=args.start,
367 stop_time=args.stop,
368 event_classes=list(event_classes))
369 parser.exit()
370
371 list_events(sid=args.sid,
372 start_time=args.start,
373 stop_time=args.stop,
374 event_classes=event_classes,
375 min_duration=args.min_duration,
376 max_duration=args.max_duration,
377 xml=args.xml,
378 csv=args.csv,
379 duplicates=args.duplicates,
380 datefmt=args.datefmt,
381 description=args.desc)
382
383
384if __name__ == '__main__':
385 main()