1/*
2 * Copyright 2009, Intel Corporation
3 * Copyright 2009, Sun Microsystems, Inc
4 *
5 * This file is part of PowerTOP
6 *
7 * This program file is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation; version 2 of the License.
10 *
11 * This program is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program in a file named COPYING; if not, write to the
18 * Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301 USA
21 *
22 * Authors:
23 *	Arjan van de Ven <arjan@linux.intel.com>
24 *	Eric C Saxe <eric.saxe@sun.com>
25 *	Aubrey Li <aubrey.li@intel.com>
26 */
27
28/*
29 * GPL Disclaimer
30 *
31 * For the avoidance of doubt, except that if any license choice other
32 * than GPL or LGPL is available it will apply instead, Sun elects to
33 * use only the General Public License version 2 (GPLv2) at this time
34 * for any software where a choice of GPL license versions is made
35 * available with the language indicating that GPLv2 or any later
36 * version may be used, or where a choice of which version of the GPL
37 * is applied is otherwise unspecified.
38 */
39
40#include <string.h>
41#include <stdlib.h>
42#include <dtrace.h>
43#include "powertop.h"
44
45static dtrace_hdl_t *dtp;
46static event_info_t *event;
47
48/*ARGSUSED*/
49static int
50pt_events_walk(const dtrace_aggdata_t *data, void *arg)
51{
52	dtrace_aggdesc_t 	*aggdesc = data->dtada_desc;
53	dtrace_recdesc_t 	*rec1, *rec2, *rec3;
54	dtrace_syminfo_t 	dts;
55	GElf_Sym 		sym;
56	uint64_t		offender_addr;
57	uint64_t 		n = 0;
58	int32_t 		*instance, *offender_cpu;
59	int 			i;
60	char 			*offense_name;
61
62	if (g_top_events >= EVENT_NUM_MAX)
63		return (0);
64
65	rec1 = &aggdesc->dtagd_rec[1];
66	rec2 = &aggdesc->dtagd_rec[2];
67
68	/*
69	 * Report interrupts
70	 */
71	if (strcmp(aggdesc->dtagd_name, "interrupts") == 0) {
72		offense_name = data->dtada_data + rec1->dtrd_offset;
73
74		/* LINTED - alignment */
75		instance = (int32_t *)(data->dtada_data + rec2->dtrd_offset);
76		(void) snprintf((char *)(event->offender_name),
77		    EVENT_NAME_MAX, "%s", "<interrupt>");
78		(void) snprintf((char *)(event->offense_name),
79		    EVENT_NAME_MAX, "%s#%d", offense_name, *instance);
80	/*
81	 * Report kernel events
82	 */
83	} else if (strcmp(aggdesc->dtagd_name, "events_k") == 0) {
84
85		(void) snprintf((char *)(event->offender_name),
86		    EVENT_NAME_MAX, "%s", "<kernel>");
87
88		/*
89		 * Casting offender_addr to the wrong type will cause
90		 * dtrace_lookup_by_addr to return 0 and the report
91		 * to show an address instead of a name.
92		 */
93		switch (g_bit_depth) {
94		case 32:
95			/* LINTED - alignment */
96			offender_addr = *(uint32_t *)(data->dtada_data +
97			    rec1->dtrd_offset);
98			break;
99		case 64:
100			/* LINTED - alignment */
101			offender_addr = *(uint64_t *)(data->dtada_data +
102			    rec1->dtrd_offset);
103			break;
104		}
105
106		/*
107		 * We have the address of the kernel callout.
108		 * Try to resolve it into a meaningful symbol
109		 */
110		if (offender_addr != NULL && dtrace_lookup_by_addr(dtp,
111		    offender_addr, &sym, &dts) == 0) {
112			(void) snprintf((char *)(event->offense_name),
113			    EVENT_NAME_MAX, "%s`%s", dts.dts_object,
114			    dts.dts_name);
115		} else {
116			(void) snprintf((char *)(event->offense_name),
117			    EVENT_NAME_MAX, "0x%llx", offender_addr);
118		}
119	/*
120	 * Report user events
121	 */
122	} else if (strcmp(aggdesc->dtagd_name, "events_u") == 0) {
123		offense_name = data->dtada_data + rec1->dtrd_offset;
124
125		(void) snprintf((char *)(event->offender_name),
126		    EVENT_NAME_MAX, "%s", offense_name);
127		(void) snprintf((char *)(event->offense_name),
128		    EVENT_NAME_MAX, "<scheduled timeout expiration>");
129	/*
130	 * Report cross calls
131	 */
132	} else if (strcmp(aggdesc->dtagd_name, "events_x") == 0) {
133		offense_name = data->dtada_data + rec1->dtrd_offset;
134
135		(void) snprintf((char *)(event->offender_name),
136		    EVENT_NAME_MAX, "%s", offense_name);
137
138		switch (g_bit_depth) {
139		case 32:
140			/* LINTED - alignment */
141			offender_addr = *(uint32_t *)(data->dtada_data +
142			    rec2->dtrd_offset);
143			break;
144		case 64:
145			/* LINTED - alignment */
146			offender_addr = *(uint64_t *)(data->dtada_data +
147			    rec2->dtrd_offset);
148			break;
149		}
150
151		/*
152		 * Try to resolve the address of the cross call function.
153		 */
154		if (offender_addr != NULL && dtrace_lookup_by_addr(dtp,
155		    offender_addr, &sym, &dts) == 0) {
156			(void) snprintf((char *)(event->offense_name),
157			    EVENT_NAME_MAX, "<xcalls> %s`%s",
158			    dts.dts_object, dts.dts_name);
159		} else {
160			(void) snprintf((char *)(event->offense_name),
161			    EVENT_NAME_MAX, "<xcalls>");
162		}
163	/*
164	 * Report cross calls from other CPUs than the one we're observing
165	 * with the -C option
166	 */
167	} else if (strcmp(aggdesc->dtagd_name, "events_xc") == 0) {
168		rec3 = &aggdesc->dtagd_rec[3];
169		offense_name = data->dtada_data + rec1->dtrd_offset;
170
171		(void) snprintf((char *)(event->offender_name),
172		    EVENT_NAME_MAX, "%s", offense_name);
173
174		switch (g_bit_depth) {
175		case 32:
176			/* LINTED - alignment */
177			offender_addr = *(uint32_t *)(data->dtada_data +
178			    rec2->dtrd_offset);
179			break;
180		case 64:
181			/* LINTED - alignment */
182			offender_addr = *(uint64_t *)(data->dtada_data +
183			    rec2->dtrd_offset);
184			break;
185		}
186		/* LINTED - alignment */
187		offender_cpu = (int32_t *)(data->dtada_data +
188		    rec3->dtrd_offset);
189
190		/*
191		 * Try to resolve the address of the cross call function.
192		 */
193		if (offender_addr != NULL && dtrace_lookup_by_addr(dtp,
194		    offender_addr, &sym, &dts) == 0) {
195			(void) snprintf((char *)(event->offense_name),
196			    EVENT_NAME_MAX, "<xcalls> %s`%s (CPU %d)",
197			    dts.dts_object, dts.dts_name, *offender_cpu);
198		} else {
199			(void) snprintf((char *)(event->offense_name),
200			    EVENT_NAME_MAX, "<xcalls> (CPU %d)",
201			    *offender_cpu);
202		}
203	/*
204	 * Report unknown events
205	 */
206	} else {
207		(void) snprintf((char *)(event->offender_name),
208		    EVENT_NAME_MAX, "%s", "<unknown>");
209		(void) snprintf((char *)(event->offense_name),
210		    EVENT_NAME_MAX, "%s", "<unknown>");
211	}
212
213	for (i = 0; i < g_ncpus; i++) {
214		/* LINTED - alignment */
215		n += *((uint64_t *)(data->dtada_percpu[i]));
216	}
217
218	event->total_count = n;
219
220	event++;
221	g_top_events++;
222
223	return (DTRACE_AGGWALK_NEXT);
224}
225
226int
227pt_events_stat_prepare(void)
228{
229	dtrace_prog_t 		*prog;
230	dtrace_proginfo_t 	info;
231	dtrace_optval_t 	statustime;
232	int 			err;
233	char			*prog_ptr;
234
235	event = g_event_info;
236
237	if ((dtp = dtrace_open(DTRACE_VERSION, 0, &err)) == NULL) {
238		pt_error("cannot open dtrace library for the event report: "
239		    "%s\n", dtrace_errmsg(NULL, err));
240		return (-1);
241	}
242
243	/*
244	 * Execute different scripts (defined in the platform specific file)
245	 * depending on user specified options.
246	 */
247	if (PT_ON_VERBOSE) {
248		prog_ptr = (char *)g_dtp_events_v;
249	} else {
250		if (PT_ON_CPU)
251			prog_ptr = (char *)g_dtp_events_c;
252		else
253			prog_ptr = (char *)g_dtp_events;
254	}
255
256	if ((prog = dtrace_program_strcompile(dtp, prog_ptr,
257	    DTRACE_PROBESPEC_NAME, 0, g_argc, g_argv)) == NULL) {
258		pt_error("failed to compile the event report program\n");
259		return (dtrace_errno(dtp));
260	}
261
262	if (dtrace_program_exec(dtp, prog, &info) == -1) {
263		pt_error("failed to enable probes for the event report\n");
264		return (dtrace_errno(dtp));
265	}
266
267	if (dtrace_setopt(dtp, "aggsize", "128k") == -1) {
268		pt_error("failed to set 'aggsize' for the event report\n");
269		return (dtrace_errno(dtp));
270	}
271
272	if (dtrace_setopt(dtp, "aggrate", "0") == -1) {
273		pt_error("failed to set 'aggrate' for the event report\n");
274		return (dtrace_errno(dtp));
275	}
276
277	if (dtrace_setopt(dtp, "aggpercpu", 0) == -1) {
278		pt_error("failed to set 'aggpercpu' for the event report\n");
279		return (dtrace_errno(dtp));
280	}
281
282	if (dtrace_go(dtp) != 0) {
283		pt_error("failed to start the event report observation\n");
284		return (dtrace_errno(dtp));
285	}
286
287	if (dtrace_getopt(dtp, "statusrate", &statustime) == -1) {
288		pt_error("failed to get 'statusrate' for the event report\n");
289		return (dtrace_errno(dtp));
290	}
291
292	return (0);
293}
294
295int
296pt_events_stat_collect(void)
297{
298	g_top_events = 0;
299	event = g_event_info;
300
301	if (dtrace_status(dtp) == -1)
302		return (-1);
303
304	if (dtrace_aggregate_snap(dtp) != 0)
305		pt_error("failed to collect data for the event report\n");
306
307	if (dtrace_aggregate_walk_keyvarsorted(dtp, pt_events_walk, NULL) != 0)
308		pt_error("failed to sort data for the event report\n");
309
310	dtrace_aggregate_clear(dtp);
311
312	return (0);
313}
314