move lttv to root
[lttv.git] / lttv / modules / gui / tutorial / tutorial.c
1 /* This file is part of the Linux Trace Toolkit viewer
2 * Copyright (C) 2005 Peter Ho
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License Version 2 as
6 * published by the Free Software Foundation;
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place - Suite 330, Boston,
16 * MA 02111-1307, USA.
17 */
18 /******************************************************************
19 Each field of the interrupt viewer is summarized as follows:
20
21 - CPUID: processor ID
22
23 - IrqId: IRQ ID
24
25 - Frequency (Hz): the number of interrupts per second (Hz).
26 We compute the total number of interrupts. Then
27 we divide it by the time interval.
28
29 - Total Duration (nsec): the sum of each interrupt duration in nsec.
30 For a given Irq ID, we sum the duration of each interrupt
31 to give us the total duration
32
33
34 *******************************************************************/
35
36
37 #include <math.h>
38 #include <glib.h>
39 #include <gtk/gtk.h>
40 #include <gdk/gdk.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <ltt/ltt.h>
45 #include <ltt/event.h>
46 #include <ltt/trace.h>
47 #include <lttv/module.h>
48 #include <lttv/hook.h>
49 #include <lttv/tracecontext.h>
50 #include <lttv/state.h>
51 #include <lttv/filter.h>
52 #include <lttvwindow/lttvwindow.h>
53 #include <lttvwindow/lttv_plugin_tab.h>
54 #include <ltt/time.h>
55
56 #include "hTutorialInsert.xpm"
57
58 #define g_info(format...) g_log (G_LOG_DOMAIN, G_LOG_LEVEL_INFO, format)
59 #define g_debug(format...) g_log (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, format)
60 #define NO_ITEMS 0
61
62 typedef struct
63 {
64 guint cpu_id;
65 guint id;
66 guint TotalNumberOfInterrupts;
67 LttTime total_duration;
68 }Irq;
69
70 typedef struct
71 {
72 guint id;
73 guint cpu_id;
74 LttTime event_time;
75 }irq_entry;
76
77 enum type_t
78 {
79 IRQ_ENTRY,
80 IRQ_EXIT
81 };
82
83 /** Array containing instanced objects. Used when module is unloaded */
84 static GSList *interrupt_data_list = NULL ;
85
86
87 #define TRACE_NUMBER 0
88
89 typedef struct _InterruptEventData
90 {
91
92 /* Graphical Widgets */
93 GtkWidget * ScrollWindow;
94 GtkListStore *ListStore;
95 GtkWidget *Hbox;
96 GtkWidget *TreeView;
97 GtkTreeSelection *SelectionTree;
98
99 Tab * tab; /* tab that contains this plug-in*/
100 LttvPluginTab * ptab;
101 LttvHooks * event_hooks;
102 LttvHooks * hooks_trace_after;
103 LttvHooks * hooks_trace_before;
104 TimeWindow time_window;
105 LttvHooksById * event_by_id_hooks;
106 GArray *IrqExit;
107 GArray *IrqEntry;
108 } InterruptEventData ;
109
110
111 /* Function prototypes */
112
113 static gboolean interrupt_update_time_window(void * hook_data, void * call_data);
114 static GtkWidget *interrupts(LttvPlugin *plugin);
115 static InterruptEventData *system_info(LttvPluginTab *ptab);
116 void interrupt_destructor(InterruptEventData *event_viewer_data);
117 static void request_event(InterruptEventData *event_data );
118 static guint64 get_interrupt_id(LttEvent *e);
119 static gboolean trace_header(void *hook_data, void *call_data);
120 static gboolean interrupt_display (void *hook_data, void *call_data);
121 static void calcul_duration(LttTime time_exit, guint cpu_id, InterruptEventData *event_data);
122 static void sum_interrupt_data(irq_entry *e, LttTime time_exit, GArray *interrupt_counters);
123 static gboolean irq_entry_callback(void *hook_data, void *call_data);
124 static gboolean irq_exit_callback(void *hook_data, void *call_data);
125 static void InterruptFree(InterruptEventData *event_viewer_data);
126 static int FrequencyInHZ(gint NumerofInterruptions, TimeWindow time_window);
127 /* Enumeration of the columns */
128 enum{
129 CPUID_COLUMN,
130 IRQ_ID_COLUMN,
131 FREQUENCY_COLUMN,
132 DURATION_COLUMN,
133 N_COLUMNS
134 };
135
136
137
138 /**
139 * init function
140 *
141 *
142 * This is the entry point of the viewer.
143 *
144 */
145 static void init()
146 {
147 g_info("interrupts: init()");
148
149 lttvwindow_register_constructor("tutorial",
150 "/",
151 "Insert Interrupts View",
152 hTutorialInsert_xpm,
153 "Insert Interrupts View",
154 interrupts);
155 }
156
157
158 /**
159 * Constructor hook
160 *
161 */
162 static GtkWidget *interrupts(LttvPlugin *plugin)
163 {
164 LttvPluginTab *ptab = LTTV_PLUGIN_TAB(plugin);
165 InterruptEventData* event_data = system_info(ptab) ;
166 if(event_data)
167 return event_data->Hbox;
168 else
169 return NULL;
170 }
171
172 /**
173 * This function initializes the Event Viewer functionnality through the
174 * GTK API.
175 */
176 InterruptEventData *system_info(LttvPluginTab *ptab)
177 {
178 LttTime end;
179 GtkTreeViewColumn *column;
180 GtkCellRenderer *renderer;
181 InterruptEventData* event_viewer_data = g_new(InterruptEventData,1) ;
182 Tab *tab = ptab->tab;
183
184 event_viewer_data->tab = tab;
185 event_viewer_data->ptab = ptab;
186
187 /*Get the current time frame from the main window */
188 event_viewer_data->time_window = lttvwindow_get_time_window(tab);
189 event_viewer_data->IrqExit = g_array_new(FALSE, FALSE, sizeof(Irq));
190 event_viewer_data->IrqEntry = g_array_new(FALSE, FALSE, sizeof(irq_entry));
191
192 /*Create tha main window for the viewer */
193 event_viewer_data->ScrollWindow = gtk_scrolled_window_new (NULL, NULL);
194 gtk_widget_show (event_viewer_data->ScrollWindow);
195 gtk_scrolled_window_set_policy(
196 GTK_SCROLLED_WINDOW(event_viewer_data->ScrollWindow),
197 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
198
199 /* Create a model for storing the data list */
200 event_viewer_data->ListStore = gtk_list_store_new (
201 N_COLUMNS, /* Total number of columns */
202 G_TYPE_INT, /* CPUID */
203 G_TYPE_INT, /* IRQ_ID */
204 G_TYPE_INT, /* Frequency */
205 G_TYPE_UINT64 /* Duration */
206 );
207
208 event_viewer_data->TreeView = gtk_tree_view_new_with_model (GTK_TREE_MODEL (event_viewer_data->ListStore));
209
210 g_object_unref (G_OBJECT (event_viewer_data->ListStore));
211
212 renderer = gtk_cell_renderer_text_new ();
213 column = gtk_tree_view_column_new_with_attributes ("CPU ID",
214 renderer,
215 "text", CPUID_COLUMN,
216 NULL);
217 gtk_tree_view_column_set_alignment (column, 0.0);
218 gtk_tree_view_column_set_fixed_width (column, 45);
219 gtk_tree_view_append_column (GTK_TREE_VIEW (event_viewer_data->TreeView), column);
220
221
222 renderer = gtk_cell_renderer_text_new ();
223 column = gtk_tree_view_column_new_with_attributes ("IRQ ID",
224 renderer,
225 "text", IRQ_ID_COLUMN,
226 NULL);
227 gtk_tree_view_column_set_alignment (column, 0.0);
228 gtk_tree_view_column_set_fixed_width (column, 220);
229 gtk_tree_view_append_column (GTK_TREE_VIEW (event_viewer_data->TreeView), column);
230
231 renderer = gtk_cell_renderer_text_new ();
232 column = gtk_tree_view_column_new_with_attributes ("Frequency (HZ)",
233 renderer,
234 "text", FREQUENCY_COLUMN,
235 NULL);
236 gtk_tree_view_column_set_alignment (column, 1.0);
237 gtk_tree_view_column_set_fixed_width (column, 220);
238 gtk_tree_view_append_column (GTK_TREE_VIEW (event_viewer_data->TreeView), column);
239
240 renderer = gtk_cell_renderer_text_new ();
241 column = gtk_tree_view_column_new_with_attributes ("Total Duration (nsec)",
242 renderer,
243 "text", DURATION_COLUMN,
244 NULL);
245 gtk_tree_view_column_set_alignment (column, 0.0);
246 gtk_tree_view_column_set_fixed_width (column, 145);
247 gtk_tree_view_append_column (GTK_TREE_VIEW (event_viewer_data->TreeView), column);
248
249 event_viewer_data->SelectionTree = gtk_tree_view_get_selection (GTK_TREE_VIEW (event_viewer_data->TreeView));
250 gtk_tree_selection_set_mode (event_viewer_data->SelectionTree, GTK_SELECTION_SINGLE);
251
252 gtk_container_add (GTK_CONTAINER (event_viewer_data->ScrollWindow), event_viewer_data->TreeView);
253
254 event_viewer_data->Hbox = gtk_hbox_new(0, 0);
255 gtk_box_pack_start(GTK_BOX(event_viewer_data->Hbox), event_viewer_data->ScrollWindow, TRUE, TRUE, 0);
256
257 gtk_widget_show(event_viewer_data->Hbox);
258 gtk_widget_show(event_viewer_data->TreeView);
259
260 interrupt_data_list = g_slist_append(interrupt_data_list, event_viewer_data);
261
262 /* Registration for time notification */
263 lttvwindow_register_time_window_notify(tab,
264 interrupt_update_time_window,
265 event_viewer_data);
266
267 g_object_set_data_full(G_OBJECT(event_viewer_data->Hbox),
268 "event_data",
269 event_viewer_data,
270 (GDestroyNotify) InterruptFree);
271
272
273 request_event(event_viewer_data );
274 return event_viewer_data;
275 }
276
277 /**
278 *
279 * For each trace in the traceset, this function:
280 * - registers a callback function to each hook
281 * - calls lttv_trace_find_hook() registers a hook function to event_by_id_hooks
282 * - calls lttvwindow_events_request() to request data in a specific
283 * time interval to the main window
284 *
285 */
286 static void request_event(InterruptEventData *event_data )
287 {
288 guint i, k, l, nb_trace;
289
290 LttvTraceHook *hook;
291
292 guint ret;
293
294 LttvTraceState *ts;
295
296 GArray *hooks;
297
298 EventsRequest *events_request;
299
300 LttvTraceHookByFacility *thf;
301
302 LttvTracesetContext *tsc = lttvwindow_get_traceset_context(event_data->tab);
303
304
305 /* Get the traceset */
306 LttvTraceset *traceset = tsc->ts;
307
308 nb_trace = lttv_traceset_number(traceset);
309
310 /* There are many traces in a traceset. Iteration for each trace. */
311 for(i = 0; i<MIN(TRACE_NUMBER+1, nb_trace);i++)
312 {
313 events_request = g_new(EventsRequest, 1);
314
315 hooks = g_array_new(FALSE, FALSE, sizeof(LttvTraceHook));
316
317 hooks = g_array_set_size(hooks, 2);
318
319 event_data->hooks_trace_before = lttv_hooks_new();
320
321 /* Registers a hook function */
322 lttv_hooks_add(event_data->hooks_trace_before, trace_header, event_data, LTTV_PRIO_DEFAULT);
323
324 event_data->hooks_trace_after = lttv_hooks_new();
325 /* Registers a hook function */
326 lttv_hooks_add(event_data->hooks_trace_after, interrupt_display, event_data, LTTV_PRIO_DEFAULT);
327 /* Get a trace state */
328 ts = (LttvTraceState *)tsc->traces[i];
329 /* Create event_by_Id hooks */
330 event_data->event_by_id_hooks = lttv_hooks_by_id_new();
331
332 /*Register event_by_id_hooks with a callback function*/
333 ret = lttv_trace_find_hook(ts->parent.t,
334 LTT_FACILITY_KERNEL, LTT_EVENT_IRQ_ENTRY,
335 LTT_FIELD_IRQ_ID, 0, 0,
336 irq_entry_callback,
337 events_request,
338 &g_array_index(hooks, LttvTraceHook, 0));
339
340 ret = lttv_trace_find_hook(ts->parent.t,
341 LTT_FACILITY_KERNEL, LTT_EVENT_IRQ_EXIT,
342 LTT_FIELD_IRQ_ID, 0, 0,
343 irq_exit_callback,
344 events_request,
345 &g_array_index(hooks, LttvTraceHook, 1));
346
347 g_assert(!ret);
348
349 /*iterate through the facility list*/
350 for(k = 0 ; k < hooks->len; k++)
351 {
352 hook = &g_array_index(hooks, LttvTraceHook, k);
353 for(l=0; l<hook->fac_list->len; l++)
354 {
355 thf = g_array_index(hook->fac_list, LttvTraceHookByFacility*, l);
356 lttv_hooks_add(lttv_hooks_by_id_find(event_data->event_by_id_hooks, thf->id),
357 thf->h,
358 event_data,
359 LTTV_PRIO_DEFAULT);
360
361 }
362 }
363
364 /* Initalize the EventsRequest structure */
365 events_request->owner = event_data;
366 events_request->viewer_data = event_data;
367 events_request->servicing = FALSE;
368 events_request->start_time = event_data->time_window.start_time;
369 events_request->start_position = NULL;
370 events_request->stop_flag = FALSE;
371 events_request->end_time = event_data->time_window.end_time;
372 events_request->num_events = G_MAXUINT;
373 events_request->end_position = NULL;
374 events_request->trace = i;
375
376 events_request->hooks = hooks;
377
378 events_request->before_chunk_traceset = NULL;
379 events_request->before_chunk_trace = event_data->hooks_trace_before;
380 events_request->before_chunk_tracefile= NULL;
381 events_request->event = NULL;
382 events_request->event_by_id = event_data->event_by_id_hooks;
383 events_request->after_chunk_tracefile = NULL;
384 events_request->after_chunk_trace = NULL;
385 events_request->after_chunk_traceset = NULL;
386 events_request->before_request = NULL;
387 events_request->after_request = event_data->hooks_trace_after;
388
389 lttvwindow_events_request(event_data->tab, events_request);
390 }
391
392 }
393
394 /**
395 * This function is called whenever an irq_entry event occurs.
396 *
397 */
398 static gboolean irq_entry_callback(void *hook_data, void *call_data)
399 {
400
401 LttTime event_time;
402 unsigned cpu_id;
403 irq_entry entry;
404 LttvTracefileContext *tfc = (LttvTracefileContext *)call_data;
405 LttvTracefileState *tfs = (LttvTracefileState *)call_data;
406 InterruptEventData *event_data = (InterruptEventData *)hook_data;
407 GArray* IrqEntry = event_data->IrqEntry;
408 LttEvent *e = ltt_tracefile_get_event(tfc->tf);
409 event_time = ltt_event_time(e);
410 cpu_id = ltt_event_cpu_id(e);
411
412 if ((ltt_time_compare(event_time,event_data->time_window.start_time) == TRUE) &&
413 (ltt_time_compare(event_data->time_window.end_time,event_time) == TRUE))
414 {
415 entry.id =get_interrupt_id(e);
416 entry.cpu_id = cpu_id;
417 entry.event_time = event_time;
418 g_array_append_val (IrqEntry, entry);
419 }
420 return FALSE;
421 }
422
423 /**
424 * This function gets the id of the interrupt. The id is stored in a dynamic structure.
425 * Refer to the print.c file for howto extract data from a dynamic structure.
426 */
427 static guint64 get_interrupt_id(LttEvent *e)
428 {
429 guint i, num_fields;
430 LttEventType *event_type;
431 LttField *element;
432 LttField *field;
433 guint64 irq_id;
434 event_type = ltt_event_eventtype(e);
435 num_fields = ltt_eventtype_num_fields(event_type);
436 for(i = 0 ; i < num_fields-1 ; i++)
437 {
438 field = ltt_eventtype_field(event_type, i);
439 irq_id = ltt_event_get_long_unsigned(e,field);
440 }
441 return irq_id;
442
443 }
444 /**
445 * This function is called whenever an irq_exit event occurs.
446 *
447 */
448 gboolean irq_exit_callback(void *hook_data, void *call_data)
449 {
450 LttTime event_time;
451 unsigned cpu_id;
452 LttvTracefileContext *tfc = (LttvTracefileContext *)call_data;
453 LttvTracefileState *tfs = (LttvTracefileState *)call_data;
454 InterruptEventData *event_data = (InterruptEventData *)hook_data;
455 LttEvent *e = ltt_tracefile_get_event(tfc->tf);
456 LttEventType *type = ltt_event_eventtype(e);
457 event_time = ltt_event_time(e);
458 cpu_id = ltt_event_cpu_id(e);
459 if ((ltt_time_compare(event_time,event_data->time_window.start_time) == TRUE) &&
460 (ltt_time_compare(event_data->time_window.end_time,event_time) == TRUE))
461 {
462 calcul_duration( event_time, cpu_id, event_data);
463
464 }
465 return FALSE;
466 }
467
468 /**
469 * This function calculates the duration of an interrupt.
470 *
471 */
472 static void calcul_duration(LttTime time_exit, guint cpu_id,InterruptEventData *event_data){
473
474 gint i, irq_id;
475 irq_entry *element;
476 LttTime duration;
477 GArray *IrqExit = event_data->IrqExit;
478 GArray *IrqEntry = event_data->IrqEntry;
479 for(i = 0; i < IrqEntry->len; i++){
480 element = &g_array_index(IrqEntry,irq_entry,i);
481 if(element->cpu_id == cpu_id)
482 {
483 sum_interrupt_data(element,time_exit, IrqExit);
484 g_array_remove_index(IrqEntry, i);
485 break;
486 }
487 }
488 }
489 /**
490 * This function calculates the total duration of an interrupt.
491 *
492 */
493 static void sum_interrupt_data(irq_entry *e, LttTime time_exit, GArray *IrqExit)
494 {
495 Irq irq;
496 Irq *element;
497 guint i;
498 LttTime duration;
499 gboolean notFound = FALSE;
500 memset ((void*)&irq, 0,sizeof(Irq));
501
502
503 if(IrqExit->len == NO_ITEMS)
504 {
505 irq.cpu_id = e->cpu_id;
506 irq.id = e->id;
507 irq.TotalNumberOfInterrupts++;
508 irq.total_duration = ltt_time_sub(time_exit, e->event_time);
509 g_array_append_val (IrqExit, irq);
510 }
511 else{
512 for(i = 0; i < IrqExit->len; i++)
513 {
514 element = &g_array_index(IrqExit, Irq, i);
515 if(element->id == e->id){
516 notFound = TRUE;
517 duration = ltt_time_sub(time_exit, e->event_time);
518 element->total_duration = ltt_time_add(element->total_duration, duration);
519 element->TotalNumberOfInterrupts++;
520 }
521 }
522
523 if(!notFound)
524 {
525 irq.cpu_id = e->cpu_id;
526 irq.id = e->id;
527 irq.TotalNumberOfInterrupts++;
528 irq.total_duration = ltt_time_sub(time_exit, e->event_time);
529 g_array_append_val (IrqExit, irq);
530 }
531 }
532 }
533
534 /**
535 * This function displays the result on the viewer
536 *
537 */
538 static gboolean interrupt_display(void *hook_data, void *call_data)
539 {
540
541 gint i;
542 Irq element;
543 LttTime average_duration;
544 GtkTreeIter iter;
545 guint64 real_data;
546 int FrequencyHZ = 0;
547
548 InterruptEventData *event_data = (InterruptEventData *)hook_data;
549 GArray *IrqExit = event_data->IrqExit;
550 gtk_list_store_clear(event_data->ListStore);
551 for(i = 0; i < IrqExit->len; i++)
552 {
553
554 element = g_array_index(IrqExit,Irq,i);
555 real_data = element.total_duration.tv_sec;
556 real_data *= NANOSECONDS_PER_SECOND;
557 real_data += element.total_duration.tv_nsec;
558
559 FrequencyHZ = FrequencyInHZ(element.TotalNumberOfInterrupts,event_data->time_window);
560
561 gtk_list_store_append (event_data->ListStore, &iter);
562
563 gtk_list_store_set (event_data->ListStore, &iter,
564 CPUID_COLUMN, element.cpu_id,
565 IRQ_ID_COLUMN, element.id,
566 FREQUENCY_COLUMN, FrequencyHZ,
567 DURATION_COLUMN, real_data,
568 -1);
569
570 }
571
572 if(event_data->IrqExit->len)
573 g_array_remove_range (event_data->IrqExit,0,event_data->IrqExit->len);
574
575 if(event_data->IrqEntry->len)
576 g_array_remove_range (event_data->IrqEntry,0,event_data->IrqEntry->len);
577 return FALSE;
578 }
579
580 /**
581 * This function converts the number of interrupts over a time window to
582 * frequency in HZ
583 */
584 static int FrequencyInHZ(gint NumerofInterruptions, TimeWindow time_window)
585 {
586 guint64 frequencyHz = 0;
587 double timeSec; // time in second
588 double result;
589 result = ltt_time_to_double(time_window.time_width);
590 timeSec = (result/NANOSECONDS_PER_SECOND); //time in second
591 frequencyHz = NumerofInterruptions / timeSec;
592 return frequencyHz;
593 }
594
595
596 /*
597 * This function is called by the main window
598 * when the time interval needs to be updated.
599 **/
600 gboolean interrupt_update_time_window(void * hook_data, void * call_data)
601 {
602 InterruptEventData *event_data = (InterruptEventData *) hook_data;
603 const TimeWindowNotifyData *time_window_nofify_data = ((const TimeWindowNotifyData *)call_data);
604 event_data->time_window = *time_window_nofify_data->new_time_window;
605 g_info("interrupts: interrupt_update_time_window()\n");
606 Tab *tab = event_data->tab;
607 lttvwindow_events_request_remove_all(tab, event_data);
608 request_event(event_data );
609 return FALSE;
610 }
611
612
613 gboolean trace_header(void *hook_data, void *call_data)
614 {
615
616 InterruptEventData *event_data = (InterruptEventData *)hook_data;
617 LttvTracefileContext *tfc = (LttvTracefileContext *)call_data;
618 LttEvent *e;
619 LttTime event_time;
620 return FALSE;
621 }
622
623 void interrupt_destroy_walk(gpointer data, gpointer user_data)
624 {
625 g_info("interrupt_destroy_walk");
626 interrupt_destructor((InterruptEventData*)data);
627
628 }
629
630 void interrupt_destructor(InterruptEventData *event_viewer_data)
631 {
632 /* May already been done by GTK window closing */
633 g_info("enter interrupt_destructor \n");
634 if(GTK_IS_WIDGET(event_viewer_data->Hbox))
635 {
636 gtk_widget_destroy(event_viewer_data->Hbox);
637 }
638 }
639
640 /**
641 This function is called when the viewer is destroyed to free hooks and memory
642 */
643 static void InterruptFree(InterruptEventData *event_viewer_data)
644 {
645 Tab *tab = event_viewer_data->tab;
646 if(tab != NULL)
647 {
648 g_array_free(event_viewer_data->IrqExit, TRUE);
649
650 g_array_free(event_viewer_data->IrqEntry, TRUE);
651
652 lttvwindow_unregister_time_window_notify(tab, interrupt_update_time_window, event_viewer_data);
653
654 lttvwindow_events_request_remove_all(event_viewer_data->tab,
655 event_viewer_data);
656
657 interrupt_data_list = g_slist_remove(interrupt_data_list, event_viewer_data);
658
659 }
660
661 }
662
663 /**
664 * plugin's destroy function
665 *
666 * This function releases the memory reserved by the module and unregisters
667 * everything that has been registered in the gtkTraceSet API.
668 */
669 static void destroy() {
670 g_info("Destroy interrupts");
671 g_slist_foreach(interrupt_data_list, interrupt_destroy_walk, NULL );
672 g_slist_free(interrupt_data_list);
673 lttvwindow_unregister_constructor(interrupts);
674
675 }
676
677 LTTV_MODULE("tutorial", "interrupts info view", \
678 "Graphical module to display interrupts performance", \
679 init, destroy, "lttvwindow")
This page took 0.065228 seconds and 4 git commands to generate.