4
%% Copyright Ericsson AB 2000-2010. All Rights Reserved.
6
%% The contents of this file are subject to the Erlang Public License,
7
%% Version 1.1, (the "License"); you may not use this file except in
8
%% compliance with the License. You should have received a copy of the
9
%% Erlang Public License along with this software. If not, it can be
10
%% retrieved online at http://www.erlang.org/.
12
%% Software distributed under the License is distributed on an "AS IS"
13
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
14
%% the License for the specific language governing rights and limitations
19
%%----------------------------------------------------------------------
20
%% Purpose: Displays details of a trace event
21
%%----------------------------------------------------------------------
23
-module(et_gs_contents_viewer).
25
-behaviour(gen_server).
28
-export([start_link/1,
31
%% gen_server callbacks
32
-export([init/1, terminate/2, code_change/3,
33
handle_call/3, handle_cast/2, handle_info/2]).
35
-include("../include/et.hrl").
36
-include("et_internal.hrl").
38
-record(state, {parent_pid, % Pid of parent process
39
viewer_pid, % Pid of viewer process
40
event_order, % Field to be used as primary key
41
event, % The original event
42
filtered_event, % Event processed by active filter
43
active_filter, % Name of the active filter
44
filters, % List of possible filters
45
win, % GUI: Window object
46
packer, % GUI: Packer object
47
width, % GUI: Window width
48
height}). % GUI: Window height
50
%%%----------------------------------------------------------------------
52
%%%----------------------------------------------------------------------
54
%%----------------------------------------------------------------------
55
%% start_link(Options) -> {ok, ContentsPid} | {error, Reason}
57
%% Start an viewer for the event contents as window in GS
59
%% Options = [option()]
63
%% {parent_pid, pid()} | % Pid of parent process
64
%% {viewer_pid, pid()} | % Pid of viewer process
65
%% {event_order, event_order()} | % Field to be used as primary key
66
%% {active_filter, atom()} | % Name of the active filter
67
%% {filter, atom(), fun()} % A named filter fun
69
%% event_order() = 'trace_ts' | 'event_ts'
70
%% ContentsPid = pid()
72
%%----------------------------------------------------------------------
74
start_link(Options) ->
75
case parse_opt(Options, default_state()) of
77
case gen_server:start_link(?MODULE, [S], []) of
78
{ok, ContentsPid} when S#state.parent_pid =/= self() ->
89
#state{parent_pid = self(),
90
viewer_pid = undefined,
91
active_filter = ?DEFAULT_FILTER_NAME,
92
filters = [?DEFAULT_FILTER],
97
Name = S#state.active_filter,
98
Filters = S#state.filters,
100
S#state.event =:= undefined ->
101
{error, {badarg, no_event}};
103
case lists:keysearch(Name, #filter.name, Filters) of
104
{value, F} when is_record(F, filter) ->
105
{ok, S#state{active_filter = Name}};
107
{error, {badarg, {no_such_filter, Name, Filters}}}
110
parse_opt([H | T], S) ->
112
{parent_pid, ParentPid} when is_pid(ParentPid) ->
113
parse_opt(T, S#state{parent_pid = ParentPid});
114
{viewer_pid, ViewerPid} when is_pid(ViewerPid) ->
115
parse_opt(T, S#state{viewer_pid = ViewerPid});
116
{event_order, trace_ts} ->
117
parse_opt(T, S#state{event_order = trace_ts});
118
{event_order, event_ts} ->
119
parse_opt(T, S#state{event_order = event_ts});
120
{event, Event} when is_record(Event, event) ->
121
parse_opt(T, S#state{event = Event});
122
{active_filter, Name} when is_atom(Name) ->
123
parse_opt(T, S#state{active_filter = Name});
124
F when is_record(F, filter),
125
is_atom(F#filter.name),
126
is_function(F#filter.function) ->
127
Filters = lists:keydelete(F#filter.name, #filter.name, S#state.filters),
128
Filters2 = lists:keysort(#filter.name, [F | Filters]),
129
parse_opt(T, S#state{filters = Filters2});
130
{width, Width} when is_integer(Width), Width > 0 ->
131
parse_opt(T, S#state{width = Width});
132
{height, Height} when is_integer(Height), Height > 0 ->
133
parse_opt(T, S#state{height = Height});
135
{error, {bad_option, Bad}}
137
parse_opt(BadList, _S) ->
138
{error, {bad_option_list, BadList}}.
140
%%----------------------------------------------------------------------
141
%% stop(ContentsPid) -> ok
143
%% Stops a contents viewer process
145
%% ContentsPid = pid()
146
%%----------------------------------------------------------------------
150
call(ContentsPid, stop).
152
call(ContentsPid, Request) ->
153
gen_server:call(ContentsPid, Request, infinity).
155
%%%----------------------------------------------------------------------
156
%%% Callback functions from gen_server
157
%%%----------------------------------------------------------------------
159
%%----------------------------------------------------------------------
161
%% Returns: {ok, State} |
162
%% {ok, State, Timeout} |
165
%%----------------------------------------------------------------------
167
init([S]) when is_record(S, state) ->
168
process_flag(trap_exit, true),
169
S2 = create_window(S),
172
%%----------------------------------------------------------------------
173
%% Func: handle_call/3
174
%% Returns: {reply, Reply, State} |
175
%% {reply, Reply, State, Timeout} |
176
%% {noreply, State} |
177
%% {noreply, State, Timeout} |
178
%% {stop, Reason, Reply, State} | (terminate/2 is called)
179
%% {stop, Reason, State} (terminate/2 is called)
180
%%----------------------------------------------------------------------
182
handle_call(stop, _From, S) ->
183
unlink(S#state.parent_pid),
184
{stop, shutdown, ok, S};
185
handle_call(Request, From, S) ->
186
ok = error_logger:format("~p(~p): handle_call(~p, ~p, ~p)~n",
187
[?MODULE, self(), Request, From, S]),
188
Reply = {error, {bad_request, Request}},
191
%%----------------------------------------------------------------------
192
%% Func: handle_cast/2
193
%% Returns: {noreply, State} |
194
%% {noreply, State, Timeout} |
195
%% {stop, Reason, State} (terminate/2 is called)
196
%%----------------------------------------------------------------------
198
handle_cast(Msg, S) ->
199
ok = error_logger:format("~p(~p): handle_cast(~p, ~p)~n",
200
[?MODULE, self(), Msg, S]),
203
%%----------------------------------------------------------------------
204
%% Func: handle_info/2
205
%% Returns: {noreply, State} |
206
%% {noreply, State, Timeout} |
207
%% {stop, Reason, State} (terminate/2 is called)
208
%%----------------------------------------------------------------------
210
handle_info({gs, Button, click, Data, _Other}, S) ->
213
gs:destroy(S#state.win),
216
Event = S#state.event,
217
Bin = list_to_binary(event_to_string(Event, S#state.event_order)),
219
case S#state.event_order of
220
trace_ts -> Event#event.trace_ts;
221
event_ts -> Event#event.event_ts
223
FileName = ["et_contents_viewer_", now_to_string(TimeStamp), ".save"],
224
file:write_file(lists:flatten(FileName), Bin),
226
_PopupMenuItem when is_record(Data, filter) ->
228
ChildState= S#state{active_filter = F#filter.name},
229
case gen_server:start_link(?MODULE, [ChildState], []) of
230
{ok, Pid} when S#state.parent_pid =/= self() ->
237
send_viewer_event(S, {delete_actors, Actors}),
240
send_viewer_event(S, {insert_actors, Actors}),
243
send_viewer_event(S, {mode, Mode}),
246
ok = error_logger:format("~p: click ~p ignored (nyi)~n",
250
handle_info({gs, _Obj, destroy,_, _}, S) ->
251
unlink(S#state.parent_pid),
252
gs:destroy(S#state.win),
254
handle_info({gs, _Obj, keypress, _, [KeySym, _Keycode, _Shift, _Control | _]}, S) ->
257
gs:destroy(S#state.win),
261
E = S#state.filtered_event,
263
send_viewer_event(S, {delete_actors, [From]}),
266
E = S#state.filtered_event,
268
send_viewer_event(S, {delete_actors, [To]}),
271
E = S#state.filtered_event,
274
send_viewer_event(S, {delete_actors, [From, To]}),
278
E = S#state.filtered_event,
280
send_viewer_event(S, {insert_actors, [From]}),
283
E = S#state.filtered_event,
285
send_viewer_event(S, {insert_actors, [To]}),
288
E = S#state.filtered_event,
291
send_viewer_event(S, {insert_actors, [From, To]}),
295
E = S#state.filtered_event,
298
First = et_collector:make_key(S#state.event_order, E),
299
Mode = {search_actors, forward, First, [From, To]},
300
send_viewer_event(S, {mode, Mode}),
303
E = S#state.filtered_event,
306
First = et_collector:make_key(S#state.event_order, E),
307
Mode = {search_actors, reverse, First, [From, To]},
308
send_viewer_event(S, {mode, Mode}),
311
send_viewer_event(S, {mode, all}),
315
case lists:keysearch(?DEFAULT_FILTER_NAME, #filter.name, S#state.filters) of
316
{value, F} when is_record(F, filter) ->
317
ChildState= S#state{active_filter = F#filter.name},
318
case gen_server:start_link(?MODULE, [ChildState], []) of
319
{ok, Pid} when S#state.parent_pid =/= self() ->
328
Int when is_integer(Int), Int > 0, Int =< 9 ->
329
case catch lists:nth(Int, S#state.filters) of
330
F when is_record(F, filter) ->
331
ChildState= S#state{active_filter = F#filter.name},
332
case gen_server:start_link(?MODULE, [ChildState], []) of
333
{ok, Pid} when S#state.parent_pid =/= self() ->
350
io:format("~p: ignored: ~p~n", [?MODULE, KeySym]),
353
handle_info({gs, _Obj, configure, [], [W, H | _]}, S) ->
354
gs:config(S#state.packer, [{width, W},{height, H}]),
355
S2 = S#state{width = W, height = H},
357
handle_info({'EXIT', Pid, Reason}, S) ->
359
Pid =:= S#state.parent_pid ->
365
handle_info(Info, S) ->
366
ok = error_logger:format("~p(~p): handle_info(~p, ~p)~n",
367
[?MODULE, self(), Info, S]),
370
%%----------------------------------------------------------------------
372
%% Purpose: Shutdown the server
373
%% Returns: any (ignored by gen_server)
374
%%----------------------------------------------------------------------
376
terminate(_Reason, _S) ->
379
%%----------------------------------------------------------------------
380
%% Func: code_change/3
381
%% Purpose: Convert process state when code is changed
382
%% Returns: {ok, NewState}
383
%%----------------------------------------------------------------------
385
code_change(_OldVsn, S, _Extra) ->
388
%%%----------------------------------------------------------------------
390
%%%----------------------------------------------------------------------
395
Name = S#state.active_filter,
396
Title = lists:concat([?MODULE, " (filter: ", Name, ")"]),
397
WinOpt = [{title, Title}, {configure, true},
398
{width, W}, {height, H}],
400
Win = gs:window(GS, WinOpt),
401
Bar = gs:menubar(Win, []),
402
create_file_menu(Bar),
403
PackerOpt = [{packer_x, [{stretch, 1}]},
404
{packer_y, [{stretch, 1}, {fixed, 25}]},
406
Packer = gs:frame(Win, PackerOpt),
407
EditorOpt = [{pack_xy, {1, 1}}, {vscroll, right}, {hscroll, bottom},
409
{bg, lightblue}, {font, {courier, 12}}],
410
Editor = gs:editor(Packer, EditorOpt),
411
FilteredEvent = config_editor(Editor, S),
412
S2 = S#state{win = Win, packer = Packer, filtered_event = FilteredEvent},
413
create_hide_menu(Bar, S2),
414
create_search_menu(Bar, S2),
415
create_filter_menu(Bar, S#state.filters),
416
gs:config(Packer, [{width, W}, {height, H}]),
417
gs:config(Win, [{map,true}, {keypress, true}]),
420
create_file_menu(Bar) ->
421
Button = gs:menubutton(Bar, [{label, {text, "File"}}]),
422
Menu = gs:menu(Button, []),
423
gs:menuitem(close, Menu, [{label, {text,"Close (c)"}}]),
424
gs:menuitem(save, Menu, [{label, {text,"Save"}}]).
426
create_filter_menu(Bar, Filters) ->
427
Button = gs:menubutton(Bar, [{label, {text, "Filters"}}]),
428
Menu = gs:menu(Button, []),
429
gs:menuitem(Menu, [{label, {text, "Select Filter"}}, {bg, lightblue}, {enable, false}]),
430
gs:menuitem(Menu, [{itemtype, separator}]),
431
Item = fun(F, N) when F#filter.name =:= ?DEFAULT_FILTER_NAME->
432
Label = lists:concat([pad_string(F#filter.name, 20), "(0)"]),
433
gs:menuitem(Menu, [{label, {text, Label}}, {data, F}]),
436
Name = F#filter.name,
437
Label = lists:concat([pad_string(Name, 20), "(", N, ")"]),
438
gs:menuitem(Menu, [{label, {text, Label}}, {data, F}]),
441
Filters2 = lists:keysort(#filter.name, Filters),
442
lists:foldl(Item, 1, Filters2),
445
create_hide_menu(Bar, S) ->
446
Button = gs:menubutton(Bar, [{label, {text, "Hide"}}]),
447
Menu = gs:menu(Button, []),
448
E = S#state.filtered_event,
452
S#state.viewer_pid =:= undefined ->
455
gs:menuitem(Menu, [{label, {text, "Hide actor in Viewer "}}, {bg, lightblue}, {enable, false}]),
456
gs:menuitem(Menu, [{itemtype, separator}]),
457
gs:menuitem({hide, [From]}, Menu, [{label, {text,"From=To (f|t|b)"}}]),
458
gs:menuitem(Menu, [{itemtype, separator}]),
459
gs:menuitem(Menu, [{label, {text, "Show actor in Viewer "}}, {bg, lightblue}, {enable, false}]),
460
gs:menuitem(Menu, [{itemtype, separator}]),
461
gs:menuitem({show, [From]}, Menu, [{label, {text,"From=To (F|T|B)"}}]);
463
gs:menuitem(Menu, [{label, {text, "Hide actor in Viewer "}}, {bg, lightblue}, {enable, false}]),
464
gs:menuitem(Menu, [{itemtype, separator}]),
465
gs:menuitem({hide, [From]}, Menu, [{label, {text,"From (f)"}}]),
466
gs:menuitem({hide, [To]}, Menu, [{label, {text,"To (t)"}}]),
467
gs:menuitem({hide, [From, To]}, Menu, [{label, {text,"Both (b)"}}]),
468
gs:menuitem(Menu, [{itemtype, separator}]),
469
gs:menuitem(Menu, [{label, {text, "Show actor in Viewer "}}, {bg, lightblue}, {enable, false}]),
470
gs:menuitem(Menu, [{itemtype, separator}]),
471
gs:menuitem({show, [From]}, Menu, [{label, {text,"From (F)"}}]),
472
gs:menuitem({show, [To]}, Menu, [{label, {text,"To (T)"}}]),
473
gs:menuitem({show, [From, To]}, Menu, [{label, {text,"Both (B)"}}])
476
create_search_menu(Bar, S) ->
477
Button = gs:menubutton(Bar, [{label, {text, "Search"}}]),
478
Menu = gs:menu(Button, []),
479
E = S#state.filtered_event,
482
gs:menuitem(Menu, [{label, {text, "Search in Viewer "}},
483
{bg, lightblue}, {enable, false}]),
484
gs:menuitem(Menu, [{itemtype, separator}]),
486
S#state.viewer_pid =:= undefined ->
489
Key = et_collector:make_key(S#state.event_order, E),
490
ModeS = {search_actors, forward, Key, [From]},
491
ModeR = {search_actors, reverse, Key, [From]},
492
gs:menuitem({mode, ModeS}, Menu, [{label, {text,"Forward from this event (s)"}}]),
493
gs:menuitem({mode, ModeR}, Menu, [{label, {text,"Reverse from this event (r)"}}]);
495
Key = et_collector:make_key(S#state.event_order, E),
496
ModeS = {search_actors, forward, Key, [From, To]},
497
ModeR = {search_actors, reverse, Key, [From, To]},
498
gs:menuitem({mode, ModeS}, Menu, [{label, {text,"Forward from this event (s)"}}]),
499
gs:menuitem({mode, ModeR}, Menu, [{label, {text,"Reverse from this event (r)"}}])
501
gs:menuitem({mode, all}, Menu, [{label, {text,"Abort search. Display all (a)"}}]).
503
config_editor(Editor, S) ->
504
Event = S#state.event,
505
Name = S#state.active_filter,
506
{value, F} = lists:keysearch(Name, #filter.name, S#state.filters),
507
FilterFun = F#filter.function,
508
case catch FilterFun(Event) of
510
do_config_editor(Editor, Event, lightblue, S#state.event_order);
511
{true, Event2} when is_record(Event2, event) ->
512
do_config_editor(Editor, Event2, lightblue, S#state.event_order);
514
do_config_editor(Editor, Event, red, S#state.event_order);
516
Contents = {bad_filter, Name, Bad},
517
BadEvent = Event#event{contents = Contents},
518
do_config_editor(Editor, BadEvent, red, S#state.event_order)
521
do_config_editor(Editor, Event, Colour, TsKey) ->
522
String = event_to_string(Event, TsKey),
523
gs:config(Editor, {insert, {'end', String}}),
524
gs:config(Editor, {enable, false}),
525
gs:config(Editor, {bg, Colour}),
528
%%%----------------------------------------------------------------------
530
%%%----------------------------------------------------------------------
532
term_to_string(Term) ->
533
case catch io_lib:format("~s", [Term]) of
534
{'EXIT', _} -> io_lib:format("~p", [Term]);
535
GoodString -> GoodString
538
now_to_string({Mega, Sec, Micro} = Now)
539
when is_integer(Mega), is_integer(Sec), is_integer(Micro) ->
540
{{Y, Mo, D}, {H, Mi, S}} = calendar:now_to_universal_time(Now),
541
lists:concat([Y, "-", Mo, "-", D, " ", H, ".", Mi, ".", S, ".", Micro]);
542
now_to_string(Other) ->
543
term_to_string(Other).
545
event_to_string(Event, TsKey) ->
546
ReportedTs = Event#event.trace_ts,
547
ParsedTs = Event#event.event_ts,
549
["DETAIL LEVEL: ", term_to_string(Event#event.detail_level),
550
"\nLABEL: ", term_to_string(Event#event.label),
551
case Event#event.from =:= Event#event.to of
553
["\nACTOR: ", term_to_string(Event#event.from)];
555
["\nFROM: ", term_to_string(Event#event.from),
556
"\nTO: ", term_to_string(Event#event.to)]
558
case ReportedTs =:= ParsedTs of
560
["\nPARSED: ", now_to_string(ParsedTs)];
564
["\nTRACE_TS: ", now_to_string(ReportedTs),
565
"\nEVENT_TS: ", now_to_string(ParsedTs)];
567
["\nEVENT_TS: ", now_to_string(ParsedTs),
568
"\nTRACE_TS: ", now_to_string(ReportedTs)]
571
"\nCONTENTS:\n\n", term_to_string(Event#event.contents)],
574
pad_string(Atom, MinLen) when is_atom(Atom) ->
575
pad_string(atom_to_list(Atom), MinLen);
576
pad_string(String, MinLen) when is_integer(MinLen), MinLen >= 0 ->
577
Len = length(String),
578
case Len >= MinLen of
582
String ++ lists:duplicate(MinLen - Len, $ )
585
send_viewer_event(S, Event) ->
586
case S#state.viewer_pid of
587
ViewerPid when is_pid(ViewerPid) ->
588
ViewerPid ! {et, Event};