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