1 /*
2  * Copyright © 2014 Pekka Paalanen <pq@iki.fi>
3  * Copyright © 2014 Collabora, Ltd.
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining
6  * a copy of this software and associated documentation files (the
7  * "Software"), to deal in the Software without restriction, including
8  * without limitation the rights to use, copy, modify, merge, publish,
9  * distribute, sublicense, and/or sell copies of the Software, and to
10  * permit persons to whom the Software is furnished to do so, subject to
11  * the following conditions:
12  *
13  * The above copyright notice and this permission notice (including the
14  * next paragraph) shall be included in all copies or substantial
15  * portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20  * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
21  * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
22  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
23  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24  * SOFTWARE.
25  */
26 
27 #include "config.h"
28 
29 #include <stdio.h>
30 #include <errno.h>
31 #include <string.h>
32 #include <time.h>
33 #include <assert.h>
34 
35 #include "timeline.h"
36 #include <libweston/libweston.h>
37 #include "file-util.h"
38 
39 struct timeline_log {
40 	clock_t clk_id;
41 	FILE *file;
42 	unsigned series;
43 	struct wl_listener compositor_destroy_listener;
44 };
45 
46 WL_EXPORT int weston_timeline_enabled_;
47 static struct timeline_log timeline_ = { CLOCK_MONOTONIC, NULL, 0 };
48 
49 static int
weston_timeline_do_open(void)50 weston_timeline_do_open(void)
51 {
52 	const char *prefix = "weston-timeline-";
53 	const char *suffix = ".log";
54 	char fname[1000];
55 
56 	timeline_.file = file_create_dated(NULL, prefix, suffix,
57 					   fname, sizeof(fname));
58 	if (!timeline_.file) {
59 		const char *msg;
60 
61 		switch (errno) {
62 		case ETIME:
63 			msg = "failure in datetime formatting";
64 			break;
65 		default:
66 			msg = strerror(errno);
67 		}
68 
69 		weston_log("Cannot open '%s*%s' for writing: %s\n",
70 			   prefix, suffix, msg);
71 		return -1;
72 	}
73 
74 	weston_log("Opened timeline file '%s'\n", fname);
75 
76 	return 0;
77 }
78 
79 static void
timeline_notify_destroy(struct wl_listener * listener,void * data)80 timeline_notify_destroy(struct wl_listener *listener, void *data)
81 {
82 	weston_timeline_close();
83 }
84 
85 void
weston_timeline_open(struct weston_compositor * compositor)86 weston_timeline_open(struct weston_compositor *compositor)
87 {
88 	if (weston_timeline_enabled_)
89 		return;
90 
91 	if (weston_timeline_do_open() < 0)
92 		return;
93 
94 	timeline_.compositor_destroy_listener.notify = timeline_notify_destroy;
95 	wl_signal_add(&compositor->destroy_signal,
96 		      &timeline_.compositor_destroy_listener);
97 
98 	if (++timeline_.series == 0)
99 		++timeline_.series;
100 
101 	weston_timeline_enabled_ = 1;
102 }
103 
104 void
weston_timeline_close(void)105 weston_timeline_close(void)
106 {
107 	if (!weston_timeline_enabled_)
108 		return;
109 
110 	weston_timeline_enabled_ = 0;
111 
112 	wl_list_remove(&timeline_.compositor_destroy_listener.link);
113 
114 	fclose(timeline_.file);
115 	timeline_.file = NULL;
116 	weston_log("Timeline log file closed.\n");
117 }
118 
119 struct timeline_emit_context {
120 	FILE *cur;
121 	FILE *out;
122 	unsigned series;
123 };
124 
125 static unsigned
timeline_new_id(void)126 timeline_new_id(void)
127 {
128 	static unsigned idc;
129 
130 	if (++idc == 0)
131 		++idc;
132 
133 	return idc;
134 }
135 
136 static int
check_series(struct timeline_emit_context * ctx,struct weston_timeline_object * to)137 check_series(struct timeline_emit_context *ctx,
138 	     struct weston_timeline_object *to)
139 {
140 	if (to->series == 0 || to->series != ctx->series) {
141 		to->series = ctx->series;
142 		to->id = timeline_new_id();
143 		return 1;
144 	}
145 
146 	if (to->force_refresh) {
147 		to->force_refresh = 0;
148 		return 1;
149 	}
150 
151 	return 0;
152 }
153 
154 static void
fprint_quoted_string(FILE * fp,const char * str)155 fprint_quoted_string(FILE *fp, const char *str)
156 {
157 	if (!str) {
158 		fprintf(fp, "null");
159 		return;
160 	}
161 
162 	fprintf(fp, "\"%s\"", str);
163 }
164 
165 static int
emit_weston_output(struct timeline_emit_context * ctx,void * obj)166 emit_weston_output(struct timeline_emit_context *ctx, void *obj)
167 {
168 	struct weston_output *o = obj;
169 
170 	if (check_series(ctx, &o->timeline)) {
171 		fprintf(ctx->out, "{ \"id\":%u, "
172 			"\"type\":\"weston_output\", \"name\":",
173 			o->timeline.id);
174 		fprint_quoted_string(ctx->out, o->name);
175 		fprintf(ctx->out, " }\n");
176 	}
177 
178 	fprintf(ctx->cur, "\"wo\":%u", o->timeline.id);
179 
180 	return 1;
181 }
182 
183 static void
check_weston_surface_description(struct timeline_emit_context * ctx,struct weston_surface * s)184 check_weston_surface_description(struct timeline_emit_context *ctx,
185 				 struct weston_surface *s)
186 {
187 	struct weston_surface *mains;
188 	char d[512];
189 	char mainstr[32];
190 
191 	if (!check_series(ctx, &s->timeline))
192 		return;
193 
194 	mains = weston_surface_get_main_surface(s);
195 	if (mains != s) {
196 		check_weston_surface_description(ctx, mains);
197 		if (snprintf(mainstr, sizeof(mainstr),
198 			     ", \"main_surface\":%u", mains->timeline.id) < 0)
199 			mainstr[0] = '\0';
200 	} else {
201 		mainstr[0] = '\0';
202 	}
203 
204 	if (!s->get_label || s->get_label(s, d, sizeof(d)) < 0)
205 		d[0] = '\0';
206 
207 	fprintf(ctx->out, "{ \"id\":%u, "
208 		"\"type\":\"weston_surface\", \"desc\":", s->timeline.id);
209 	fprint_quoted_string(ctx->out, d[0] ? d : NULL);
210 	fprintf(ctx->out, "%s }\n", mainstr);
211 }
212 
213 static int
emit_weston_surface(struct timeline_emit_context * ctx,void * obj)214 emit_weston_surface(struct timeline_emit_context *ctx, void *obj)
215 {
216 	struct weston_surface *s = obj;
217 
218 	check_weston_surface_description(ctx, s);
219 	fprintf(ctx->cur, "\"ws\":%u", s->timeline.id);
220 
221 	return 1;
222 }
223 
224 static int
emit_vblank_timestamp(struct timeline_emit_context * ctx,void * obj)225 emit_vblank_timestamp(struct timeline_emit_context *ctx, void *obj)
226 {
227 	struct timespec *ts = obj;
228 
229 	fprintf(ctx->cur, "\"vblank\":[%" PRId64 ", %ld]",
230 		(int64_t)ts->tv_sec, ts->tv_nsec);
231 
232 	return 1;
233 }
234 
235 static int
emit_gpu_timestamp(struct timeline_emit_context * ctx,void * obj)236 emit_gpu_timestamp(struct timeline_emit_context *ctx, void *obj)
237 {
238 	struct timespec *ts = obj;
239 
240 	fprintf(ctx->cur, "\"gpu\":[%" PRId64 ", %ld]",
241 		(int64_t)ts->tv_sec, ts->tv_nsec);
242 
243 	return 1;
244 }
245 
246 typedef int (*type_func)(struct timeline_emit_context *ctx, void *obj);
247 
248 static const type_func type_dispatch[] = {
249 	[TLT_OUTPUT] = emit_weston_output,
250 	[TLT_SURFACE] = emit_weston_surface,
251 	[TLT_VBLANK] = emit_vblank_timestamp,
252 	[TLT_GPU] = emit_gpu_timestamp,
253 };
254 
255 WL_EXPORT void
weston_timeline_point(const char * name,...)256 weston_timeline_point(const char *name, ...)
257 {
258 	va_list argp;
259 	struct timespec ts;
260 	enum timeline_type otype;
261 	void *obj;
262 	char buf[512];
263 	struct timeline_emit_context ctx;
264 
265 	clock_gettime(timeline_.clk_id, &ts);
266 
267 	ctx.out = timeline_.file;
268 	ctx.cur = fmemopen(buf, sizeof(buf), "w");
269 	ctx.series = timeline_.series;
270 
271 	if (!ctx.cur) {
272 		weston_log("Timeline error in fmemopen, closing.\n");
273 		weston_timeline_close();
274 		return;
275 	}
276 
277 	fprintf(ctx.cur, "{ \"T\":[%" PRId64 ", %ld], \"N\":\"%s\"",
278 		(int64_t)ts.tv_sec, ts.tv_nsec, name);
279 
280 	va_start(argp, name);
281 	while (1) {
282 		otype = va_arg(argp, enum timeline_type);
283 		if (otype == TLT_END)
284 			break;
285 
286 		obj = va_arg(argp, void *);
287 		if (type_dispatch[otype]) {
288 			fprintf(ctx.cur, ", ");
289 			type_dispatch[otype](&ctx, obj);
290 		}
291 	}
292 	va_end(argp);
293 
294 	fprintf(ctx.cur, " }\n");
295 	fflush(ctx.cur);
296 	if (ferror(ctx.cur)) {
297 		weston_log("Timeline error in constructing entry, closing.\n");
298 		weston_timeline_close();
299 	} else {
300 		fprintf(ctx.out, "%s", buf);
301 	}
302 
303 	fclose(ctx.cur);
304 }
305