| /* |
| * Copyright © 2014 Pekka Paalanen <pq@iki.fi> |
| * Copyright © 2014, 2019 Collabora, Ltd. |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining |
| * a copy of this software and associated documentation files (the |
| * "Software"), to deal in the Software without restriction, including |
| * without limitation the rights to use, copy, modify, merge, publish, |
| * distribute, sublicense, and/or sell copies of the Software, and to |
| * permit persons to whom the Software is furnished to do so, subject to |
| * the following conditions: |
| * |
| * The above copyright notice and this permission notice (including the |
| * next paragraph) shall be included in all copies or substantial |
| * portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
| * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
| * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
| * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| * SOFTWARE. |
| */ |
| |
| #include "config.h" |
| |
| #include <stdio.h> |
| #include <errno.h> |
| #include <string.h> |
| #include <time.h> |
| #include <assert.h> |
| |
| #include <libweston/libweston.h> |
| #include <libweston/weston-log.h> |
| #include "shared/zalloc.h" |
| #include "timeline.h" |
| #include "weston-log-internal.h" |
| |
| /** |
| * Timeline itself is not a subscriber but a scope (a producer of data), and it |
| * re-routes the data it produces to all the subscriptions (and implicitly |
| * to the subscribers) using a subscription iteration to go through all of them. |
| * |
| * Public API: |
| * * weston_timeline_refresh_subscription_objects() - allows outside parts of |
| * libweston notify/signal timeline code about the fact that underlying object |
| * has suffered some modifications and needs to re-emit the object ID. |
| * * weston_log_timeline_point() - which will disseminate data to all |
| * subscriptions |
| * |
| * Do note that only weston_timeline_refresh_subscription_objects() |
| * is exported in libweston. |
| * |
| * Destruction of the objects assigned to each underlying objects happens in |
| * two places: one in the logging framework callback of the log scope |
| * ('destroy_subscription'), and secondly, when the object itself gets |
| * destroyed. |
| * |
| * timeline_emit_context - For each subscription this object will be created to |
| * store a buffer when the object itself will be written and a subscription, |
| * which will be used to force the object ID if there is a need to do so (the |
| * underlying object has been refreshed, or better said has suffered some |
| * modification). Data written to a subscription will be flushed before the |
| * data written to the FILE *. |
| * |
| * @param cur a FILE * |
| * @param subscription a pointer to an already created subscription |
| * |
| * @ingroup internal-log |
| * @sa weston_timeline_point |
| */ |
| struct timeline_emit_context { |
| FILE *cur; |
| struct weston_log_subscription *subscription; |
| }; |
| |
| /** Create a timeline subscription and hang it off the subscription |
| * |
| * Called when the subscription is created. |
| * |
| * @ingroup internal-log |
| */ |
| void |
| weston_timeline_create_subscription(struct weston_log_subscription *sub, |
| void *user_data) |
| { |
| struct weston_timeline_subscription *tl_sub = zalloc(sizeof(*tl_sub)); |
| if (!tl_sub) |
| return; |
| |
| wl_list_init(&tl_sub->objects); |
| |
| /* attach this timeline_subscription to it */ |
| weston_log_subscription_set_data(sub, tl_sub); |
| } |
| |
| static void |
| weston_timeline_destroy_subscription_object(struct weston_timeline_subscription_object *sub_obj) |
| { |
| /* remove the notify listener */ |
| wl_list_remove(&sub_obj->destroy_listener.link); |
| sub_obj->destroy_listener.notify = NULL; |
| |
| wl_list_remove(&sub_obj->subscription_link); |
| free(sub_obj); |
| } |
| |
| /** Destroy the timeline subscription and all timeline subscription objects |
| * associated with it. |
| * |
| * Called when (before) the subscription is destroyed. |
| * |
| * @ingroup internal-log |
| */ |
| void |
| weston_timeline_destroy_subscription(struct weston_log_subscription *sub, |
| void *user_data) |
| { |
| struct weston_timeline_subscription *tl_sub = |
| weston_log_subscription_get_data(sub); |
| struct weston_timeline_subscription_object *sub_obj, *tmp_sub_obj; |
| |
| if (!tl_sub) |
| return; |
| |
| wl_list_for_each_safe(sub_obj, tmp_sub_obj, |
| &tl_sub->objects, subscription_link) |
| weston_timeline_destroy_subscription_object(sub_obj); |
| |
| free(tl_sub); |
| } |
| |
| static bool |
| weston_timeline_check_object_refresh(struct weston_timeline_subscription_object *obj) |
| { |
| if (obj->force_refresh == true) { |
| obj->force_refresh = false; |
| return true; |
| } |
| return false; |
| } |
| |
| static struct weston_timeline_subscription_object * |
| weston_timeline_subscription_search(struct weston_timeline_subscription *tl_sub, |
| void *object) |
| { |
| struct weston_timeline_subscription_object *sub_obj; |
| |
| wl_list_for_each(sub_obj, &tl_sub->objects, subscription_link) |
| if (sub_obj->object == object) |
| return sub_obj; |
| |
| return NULL; |
| } |
| |
| static struct weston_timeline_subscription_object * |
| weston_timeline_subscription_object_create(void *object, |
| struct weston_timeline_subscription *tm_sub) |
| { |
| struct weston_timeline_subscription_object *sub_obj; |
| |
| sub_obj = zalloc(sizeof(*sub_obj)); |
| sub_obj->id = ++tm_sub->next_id; |
| sub_obj->object = object; |
| |
| /* when the object is created so that it has the chance to display the |
| * object ID, we set the refresh status; it will only be re-freshed by |
| * the backend (or part parts) when the underlying objects has suffered |
| * modifications */ |
| sub_obj->force_refresh = true; |
| |
| wl_list_insert(&tm_sub->objects, &sub_obj->subscription_link); |
| |
| return sub_obj; |
| } |
| |
| static void |
| weston_timeline_destroy_subscription_object_notify(struct wl_listener *listener, void *data) |
| { |
| struct weston_timeline_subscription_object *sub_obj; |
| |
| sub_obj = wl_container_of(listener, sub_obj, destroy_listener); |
| weston_timeline_destroy_subscription_object(sub_obj); |
| } |
| |
| static struct weston_timeline_subscription_object * |
| weston_timeline_subscription_output_ensure(struct weston_timeline_subscription *tl_sub, |
| struct weston_output *output) |
| { |
| struct weston_timeline_subscription_object *sub_obj; |
| |
| sub_obj = weston_timeline_subscription_search(tl_sub, output); |
| if (!sub_obj) { |
| sub_obj = weston_timeline_subscription_object_create(output, tl_sub); |
| |
| sub_obj->destroy_listener.notify = |
| weston_timeline_destroy_subscription_object_notify; |
| wl_signal_add(&output->destroy_signal, |
| &sub_obj->destroy_listener); |
| } |
| return sub_obj; |
| } |
| |
| static struct weston_timeline_subscription_object * |
| weston_timeline_subscription_surface_ensure(struct weston_timeline_subscription *tl_sub, |
| struct weston_surface *surface) |
| { |
| struct weston_timeline_subscription_object *sub_obj; |
| |
| sub_obj = weston_timeline_subscription_search(tl_sub, surface); |
| if (!sub_obj) { |
| sub_obj = weston_timeline_subscription_object_create(surface, tl_sub); |
| |
| sub_obj->destroy_listener.notify = |
| weston_timeline_destroy_subscription_object_notify; |
| wl_signal_add(&surface->destroy_signal, |
| &sub_obj->destroy_listener); |
| } |
| |
| return sub_obj; |
| } |
| |
| static void |
| fprint_quoted_string(struct weston_log_subscription *sub, const char *str) |
| { |
| if (!str) { |
| weston_log_subscription_printf(sub, "null"); |
| return; |
| } |
| |
| weston_log_subscription_printf(sub, "\"%s\"", str); |
| } |
| |
| static void |
| emit_weston_output_print_id(struct weston_log_subscription *sub, |
| struct weston_timeline_subscription_object *sub_obj, |
| const char *name) |
| { |
| if (!weston_timeline_check_object_refresh(sub_obj)) |
| return; |
| |
| weston_log_subscription_printf(sub, "{ \"id\":%u, " |
| "\"type\":\"weston_output\", \"name\":", sub_obj->id); |
| fprint_quoted_string(sub, name); |
| weston_log_subscription_printf(sub, " }\n"); |
| } |
| |
| static int |
| emit_weston_output(struct timeline_emit_context *ctx, void *obj) |
| { |
| struct weston_log_subscription *sub = ctx->subscription; |
| struct weston_output *output = obj; |
| struct weston_timeline_subscription_object *sub_obj; |
| struct weston_timeline_subscription *tl_sub; |
| |
| tl_sub = weston_log_subscription_get_data(sub); |
| sub_obj = weston_timeline_subscription_output_ensure(tl_sub, output); |
| emit_weston_output_print_id(sub, sub_obj, output->name); |
| |
| assert(sub_obj->id != 0); |
| fprintf(ctx->cur, "\"wo\":%u", sub_obj->id); |
| |
| return 1; |
| } |
| |
| |
| static void |
| check_weston_surface_description(struct weston_log_subscription *sub, |
| struct weston_surface *s, |
| struct weston_timeline_subscription *tm_sub, |
| struct weston_timeline_subscription_object *sub_obj) |
| { |
| struct weston_surface *mains; |
| char d[512]; |
| char mainstr[32]; |
| |
| if (!weston_timeline_check_object_refresh(sub_obj)) |
| return; |
| |
| mains = weston_surface_get_main_surface(s); |
| if (mains != s) { |
| struct weston_timeline_subscription_object *new_sub_obj; |
| |
| new_sub_obj = weston_timeline_subscription_surface_ensure(tm_sub, mains); |
| check_weston_surface_description(sub, mains, tm_sub, new_sub_obj); |
| if (snprintf(mainstr, sizeof(mainstr), ", \"main_surface\":%u", |
| new_sub_obj->id) < 0) |
| mainstr[0] = '\0'; |
| } else { |
| mainstr[0] = '\0'; |
| } |
| |
| if (!s->get_label || s->get_label(s, d, sizeof(d)) < 0) |
| d[0] = '\0'; |
| |
| weston_log_subscription_printf(sub, "{ \"id\":%u, " |
| "\"type\":\"weston_surface\", \"desc\":", |
| sub_obj->id); |
| fprint_quoted_string(sub, d[0] ? d : NULL); |
| weston_log_subscription_printf(sub, "%s }\n", mainstr); |
| } |
| |
| static int |
| emit_weston_surface(struct timeline_emit_context *ctx, void *obj) |
| { |
| struct weston_log_subscription *sub = ctx->subscription; |
| struct weston_surface *surface = obj; |
| struct weston_timeline_subscription_object *sub_obj; |
| struct weston_timeline_subscription *tl_sub; |
| |
| tl_sub = weston_log_subscription_get_data(sub); |
| sub_obj = weston_timeline_subscription_surface_ensure(tl_sub, surface); |
| check_weston_surface_description(sub, surface, tl_sub, sub_obj); |
| |
| assert(sub_obj->id != 0); |
| fprintf(ctx->cur, "\"ws\":%u", sub_obj->id); |
| |
| return 1; |
| } |
| |
| static int |
| emit_vblank_timestamp(struct timeline_emit_context *ctx, void *obj) |
| { |
| struct timespec *ts = obj; |
| |
| fprintf(ctx->cur, "\"vblank_monotonic\":[%" PRId64 ", %ld]", |
| (int64_t)ts->tv_sec, ts->tv_nsec); |
| |
| return 1; |
| } |
| |
| static int |
| emit_gpu_timestamp(struct timeline_emit_context *ctx, void *obj) |
| { |
| struct timespec *ts = obj; |
| |
| fprintf(ctx->cur, "\"gpu\":[%" PRId64 ", %ld]", |
| (int64_t)ts->tv_sec, ts->tv_nsec); |
| |
| return 1; |
| } |
| |
| static struct weston_timeline_subscription_object * |
| weston_timeline_get_subscription_object(struct weston_log_subscription *sub, |
| void *object) |
| { |
| struct weston_timeline_subscription *tl_sub; |
| |
| tl_sub = weston_log_subscription_get_data(sub); |
| if (!tl_sub) |
| return NULL; |
| |
| return weston_timeline_subscription_search(tl_sub, object); |
| } |
| |
| /** Sets (on) the timeline subscription object refresh status. |
| * |
| * This function 'notifies' timeline to print the object ID. The timeline code |
| * will reset it back, so there's no need for users to do anything about it. |
| * |
| * Can be used from outside libweston. |
| * |
| * @param wc a weston_compositor instance |
| * @param object the underyling object |
| * |
| * @ingroup log |
| */ |
| WL_EXPORT void |
| weston_timeline_refresh_subscription_objects(struct weston_compositor *wc, |
| void *object) |
| { |
| struct weston_log_subscription *sub = NULL; |
| |
| while ((sub = weston_log_subscription_iterate(wc->timeline, sub))) { |
| struct weston_timeline_subscription_object *sub_obj; |
| |
| sub_obj = weston_timeline_get_subscription_object(sub, object); |
| if (sub_obj) |
| sub_obj->force_refresh = true; |
| } |
| } |
| |
| typedef int (*type_func)(struct timeline_emit_context *ctx, void *obj); |
| |
| static const type_func type_dispatch[] = { |
| [TLT_OUTPUT] = emit_weston_output, |
| [TLT_SURFACE] = emit_weston_surface, |
| [TLT_VBLANK] = emit_vblank_timestamp, |
| [TLT_GPU] = emit_gpu_timestamp, |
| }; |
| |
| /** Disseminates the message to all subscriptions of the scope \c |
| * timeline_scope |
| * |
| * The TL_POINT() is a wrapper over this function, but it uses the weston_compositor |
| * instance to pass the timeline scope. |
| * |
| * @param timeline_scope the timeline scope |
| * @param name the name of the timeline point. Interpretable by the tool reading |
| * the output (wesgr). |
| * |
| * @ingroup log |
| */ |
| WL_EXPORT void |
| weston_timeline_point(struct weston_log_scope *timeline_scope, |
| const char *name, ...) |
| { |
| struct timespec ts; |
| enum timeline_type otype; |
| void *obj; |
| char buf[512]; |
| struct weston_log_subscription *sub = NULL; |
| |
| if (!weston_log_scope_is_enabled(timeline_scope)) |
| return; |
| |
| clock_gettime(CLOCK_MONOTONIC, &ts); |
| |
| while ((sub = weston_log_subscription_iterate(timeline_scope, sub))) { |
| va_list argp; |
| struct timeline_emit_context ctx = {}; |
| |
| memset(buf, 0, sizeof(buf)); |
| ctx.cur = fmemopen(buf, sizeof(buf), "w"); |
| ctx.subscription = sub; |
| |
| if (!ctx.cur) { |
| weston_log("Timeline error in fmemopen, closing.\n"); |
| return; |
| } |
| |
| fprintf(ctx.cur, "{ \"T\":[%" PRId64 ", %ld], \"N\":\"%s\"", |
| (int64_t)ts.tv_sec, ts.tv_nsec, name); |
| |
| va_start(argp, name); |
| while (1) { |
| otype = va_arg(argp, enum timeline_type); |
| if (otype == TLT_END) |
| break; |
| |
| obj = va_arg(argp, void *); |
| if (type_dispatch[otype]) { |
| fprintf(ctx.cur, ", "); |
| type_dispatch[otype](&ctx, obj); |
| } |
| } |
| va_end(argp); |
| |
| fprintf(ctx.cur, " }\n"); |
| fflush(ctx.cur); |
| if (ferror(ctx.cur)) { |
| weston_log("Timeline error in constructing entry, closing.\n"); |
| } else { |
| weston_log_subscription_printf(ctx.subscription, "%s", buf); |
| } |
| |
| fclose(ctx.cur); |
| |
| } |
| } |