You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							461 lines
						
					
					
						
							13 KiB
						
					
					
				
			
		
		
	
	
							461 lines
						
					
					
						
							13 KiB
						
					
					
				/*
 | 
						|
 * 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 "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 underlying 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);
 | 
						|
 | 
						|
	}
 | 
						|
}
 | 
						|
 |