svn commit: r365449 - head/sbin/rcorder

Andrey V. Elsukov ae at FreeBSD.org
Tue Sep 8 10:36:12 UTC 2020


Author: ae
Date: Tue Sep  8 10:36:11 2020
New Revision: 365449
URL: https://svnweb.freebsd.org/changeset/base/365449

Log:
  Add a few features to rcorder:
  
  o Enhance dependency loop logging: print full chain instead of the
    last link competing the loop;
  o Add -g option to generate dependency graph suitable for GraphViz
    visualization, loops and other graph generation issues are highlighted
    automatically;
  o Add -p option that enables grouping items that can be processed in
    parallel.
  
  Submitted by:	Boris Lytochkin <lytboris at gmail>
  Reviewed by:	melifaro
  MFC after:	1 week
  Differential Revision:	https://reviews.freebsd.org/D25389

Modified:
  head/sbin/rcorder/rcorder.8
  head/sbin/rcorder/rcorder.c

Modified: head/sbin/rcorder/rcorder.8
==============================================================================
--- head/sbin/rcorder/rcorder.8	Tue Sep  8 07:37:45 2020	(r365448)
+++ head/sbin/rcorder/rcorder.8	Tue Sep  8 10:36:11 2020	(r365449)
@@ -31,7 +31,7 @@
 .\"
 .\" $FreeBSD$
 .\"
-.Dd June 22, 2020
+.Dd September 8, 2020
 .Dt RCORDER 8
 .Os
 .Sh NAME
@@ -39,6 +39,7 @@
 .Nd print a dependency ordering of interdependent files
 .Sh SYNOPSIS
 .Nm
+.Op Fl gp
 .Op Fl k Ar keep
 .Op Fl s Ar skip
 .Ar
@@ -95,6 +96,9 @@ is reached, parsing stops.
 .Pp
 The options are as follows:
 .Bl -tag -width "-k keep"
+.It Fl g
+Produce a GraphViz (.dot) of the complete dependency graph instead of
+plaintext calling order list.
 .It Fl k Ar keep
 Add the specified keyword to the
 .Dq "keep list" .
@@ -102,6 +106,9 @@ If any
 .Fl k
 option is given, only those files containing the matching keyword are listed.
 This option can be specified multiple times.
+.It Fl p
+Generate ordering suitable for parallel startup, placing files that can be
+executed simultaneously on the same line.
 .It Fl s Ar skip
 Add the specified keyword to the
 .Dq "skip list" .
@@ -178,19 +185,46 @@ The
 utility may print one of the following error messages and exit with a non-zero
 status if it encounters an error while processing the file list.
 .Bl -diag
-.It "Requirement %s has no providers, aborting."
+.It "Requirement %s in file %s has no providers."
 No file has a
 .Ql PROVIDE
 line corresponding to a condition present in a
 .Ql REQUIRE
 line in another file.
-.It "Circular dependency on provision %s, aborting."
+.It "Circular dependency on provision %s in file %s."
 A set of files has a circular dependency which was detected while
 processing the stated condition.
-.It "Circular dependency on file %s, aborting."
+Loop visualization follows this message.
+.It "Circular dependency on file %s."
 A set of files has a circular dependency which was detected while
 processing the stated file.
+.It "%s was seen in circular dependencies for %d times."
+Each node that was a part of circular dependency loops reports total number of
+such encounters.
+Start with files having biggest counter when fighting with broken dependencies.
 .El
+.Sh DIAGNOSTICS WITH GRAPHVIZ
+Direct dependency is drawn with solid line,
+.Ql BEFORE
+dependency is drawn as a dashed line.
+Each node of a graph represents an item from
+.Ql PROVIDE
+lines.
+In case there are more than one file providing an item, a list of filenames
+shortened with
+.Xr basename 3
+is shown.
+Shortened filenames are also shown in case
+.Ql PROVIDE
+item does not match file name.
+.Pp
+Edges and nodes where circular dependencies were detected are drawn bold red.
+If a file has an item in
+.Ql REQUIRE
+or in
+.Ql BEFORE
+that could not be provided,
+this missing provider and the requirement will be drawn bold red as well.
 .Sh SEE ALSO
 .Xr acpiconf 8 ,
 .Xr rc 8 ,

Modified: head/sbin/rcorder/rcorder.c
==============================================================================
--- head/sbin/rcorder/rcorder.c	Tue Sep  8 07:37:45 2020	(r365448)
+++ head/sbin/rcorder/rcorder.c	Tue Sep  8 10:36:11 2020	(r365449)
@@ -9,6 +9,8 @@
  * All rights reserved.
  * Copyright (c) 1998
  * 	Perry E. Metzger.  All rights reserved.
+ * Copyright (c) 2020
+ *     Boris N. Lytochkin. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -48,6 +50,8 @@ __FBSDID("$FreeBSD$");
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+#include <libgen.h>
+#include <stdbool.h>
 
 #include "ealloc.h"
 #include "sprite.h"
@@ -75,17 +79,21 @@ static int debug = 0;
 #define KEYWORDS_STR	"# KEYWORDS:"
 #define KEYWORDS_LEN	(sizeof(KEYWORDS_STR) - 1)
 
+#define	FAKE_PROV_NAME	"fake_prov_"
+
 static int exit_code;
 static int file_count;
 static char **file_list;
 
-typedef int bool;
 #define TRUE 1
 #define FALSE 0
 typedef bool flag;
 #define SET TRUE
 #define RESET FALSE
 
+static flag do_graphviz = false;
+static flag do_parallel = false;
+
 static Hash_Table provide_hash_s, *provide_hash;
 
 typedef struct provnode provnode;
@@ -97,12 +105,14 @@ typedef struct strnodelist strnodelist;
 struct provnode {
 	flag		head;
 	flag		in_progress;
+	int		sequence;
 	filenode	*fnode;
 	provnode	*next, *last;
 };
 
 struct f_provnode {
 	provnode	*pnode;
+	Hash_Entry	*entry;
 	f_provnode	*next;
 };
 
@@ -124,19 +134,23 @@ struct filenode {
 	f_reqnode	*req_list;
 	f_provnode	*prov_list;
 	strnodelist	*keyword_list;
+	int		issues_count;
+	int		sequence;
 };
 
-static filenode fn_head_s, *fn_head;
+static filenode fn_head_s, *fn_head, **fn_seqlist;
+static int max_sequence = 0;
 
 static strnodelist *bl_list;
 static strnodelist *keep_list;
 static strnodelist *skip_list;
 
-static void do_file(filenode *fnode);
+static void do_file(filenode *fnode, strnodelist *);
 static void strnode_add(strnodelist **, char *, filenode *);
 static int skip_ok(filenode *fnode);
 static int keep_ok(filenode *fnode);
-static void satisfy_req(f_reqnode *rnode, char *filename);
+static char *generate_loop_for_req(strnodelist *, provnode *, filenode *);
+static void satisfy_req(f_reqnode *rnode, filenode *fnode, strnodelist *);
 static void crunch_file(char *);
 static void parse_require(filenode *, char *);
 static void parse_provide(filenode *, char *);
@@ -151,6 +165,12 @@ static void insert_before(void);
 static Hash_Entry *make_fake_provision(filenode *);
 static void crunch_all_files(void);
 static void initialize(void);
+static void generate_graphviz_header(void);
+static void generate_graphviz_footer(void);
+static void generate_graphviz_file_links(Hash_Entry *, filenode *);
+static void generate_graphviz_providers(void);
+static inline int is_fake_prov(const char *);
+static int sequence_cmp(const void *, const void *);
 static void generate_ordering(void);
 
 int
@@ -158,7 +178,7 @@ main(int argc, char *argv[])
 {
 	int ch;
 
-	while ((ch = getopt(argc, argv, "dk:s:")) != -1)
+	while ((ch = getopt(argc, argv, "dgk:ps:")) != -1)
 		switch (ch) {
 		case 'd':
 #ifdef DEBUG
@@ -167,9 +187,15 @@ main(int argc, char *argv[])
 			warnx("debugging not compiled in, -d ignored");
 #endif
 			break;
+		case 'g':
+			do_graphviz = true;
+			break;
 		case 'k':
 			strnode_add(&keep_list, optarg, 0);
 			break;
+		case 'p':
+			do_parallel = true;
+			break;
 		case 's':
 			strnode_add(&skip_list, optarg, 0);
 			break;
@@ -186,10 +212,13 @@ main(int argc, char *argv[])
 	DPRINTF((stderr, "parse_args\n"));
 	initialize();
 	DPRINTF((stderr, "initialize\n"));
+	generate_graphviz_header();
 	crunch_all_files();
 	DPRINTF((stderr, "crunch_all_files\n"));
+	generate_graphviz_providers();
 	generate_ordering();
 	DPRINTF((stderr, "generate_ordering\n"));
+	generate_graphviz_footer();
 
 	exit(exit_code);
 }
@@ -295,6 +324,7 @@ add_provide(filenode *fnode, char *s)
 		head->head = SET;
 		head->in_progress = RESET;
 		head->fnode = NULL;
+		head->sequence = 0;
 		head->last = head->next = NULL;
 		Hash_SetValue(entry, head);
 	}
@@ -350,6 +380,7 @@ add_provide(filenode *fnode, char *s)
 
 	f_pnode = emalloc(sizeof(*f_pnode));
 	f_pnode->pnode = pnode;
+	f_pnode->entry = entry;
 	f_pnode->next = fnode->prov_list;
 	fnode->prov_list = f_pnode;
 }
@@ -522,7 +553,7 @@ make_fake_provision(filenode *node)
 	char buffer[30];
 
 	do {
-		snprintf(buffer, sizeof buffer, "fake_prov_%08d", i++);
+		snprintf(buffer, sizeof buffer, FAKE_PROV_NAME "%08d", i++);
 		entry = Hash_CreateEntry(provide_hash, buffer, &new);
 	} while (new == 0);
 	head = emalloc(sizeof(*head));
@@ -543,6 +574,7 @@ make_fake_provision(filenode *node)
 		pnode->next->last = pnode;
 
 	f_pnode = emalloc(sizeof(*f_pnode));
+	f_pnode->entry = entry;
 	f_pnode->pnode = pnode;
 	f_pnode->next = node->prov_list;
 	node->prov_list = f_pnode;
@@ -575,6 +607,11 @@ insert_before(void)
 		if (new == 1)
 			warnx("file `%s' is before unknown provision `%s'", bl_list->node->filename, bl_list->s);
 
+		if (new == 1 && do_graphviz == true)
+			generate_graphviz_file_links(
+			    Hash_FindEntry(provide_hash, bl_list->s),
+			    bl_list->node);
+
 		for (pnode = Hash_GetValue(entry); pnode; pnode = pnode->next) {
 			if (pnode->head)
 				continue;
@@ -605,7 +642,134 @@ crunch_all_files(void)
 	insert_before();
 }
 
+static inline int
+is_fake_prov(const char *name)
+{
+
+	return (name == strstr(name, FAKE_PROV_NAME));
+}
+
+/* loop though provide list of vnode drawing all non-fake dependencies */
+static void
+generate_graphviz_file_links(Hash_Entry *entry, filenode *fnode)
+{
+	char *dep_name, *fname;
+	provnode *head;
+	f_provnode *fpnode, *rfpnode;
+	int is_before = 0;
+
+	dep_name = Hash_GetKey(entry);
+	if (is_fake_prov(dep_name))
+		is_before = 1;
+	head = Hash_GetValue(entry);
+
+	for (fpnode = fnode->prov_list; fpnode && fpnode->entry;
+	    fpnode = fpnode->next) {
+		fname = Hash_GetKey(fpnode->entry);
+		if (is_fake_prov(fname))
+			continue;
+		rfpnode = NULL;
+		do {
+			if (rfpnode)
+				dep_name = Hash_GetKey(rfpnode->entry);
+			else
+				dep_name = Hash_GetKey(entry);
+
+			if (!is_fake_prov(dep_name)) {
+				printf("\"%s\" -> \"%s\" [%s%s];\n",
+				    fname, dep_name,
+				    /* edge style */
+				    (is_before ? "style=dashed" : "style=solid"),
+				    /* circular dep? */
+				    ((head == NULL ||
+				    (head->next && head->in_progress == SET)) ?
+				    ", color=red, penwidth=4" : ""));
+				if (rfpnode == NULL)
+					break;
+			}
+			/* dependency is solved already */
+			if (head == NULL || head->next == NULL)
+				break;
+
+			if (rfpnode == NULL)
+				rfpnode = head->next->fnode->prov_list;
+			else
+				rfpnode = rfpnode->next;
+		} while (rfpnode);
+	}
+}
+
 /*
+ * Walk the stack, find the looping point and generate traceback.
+ * NULL is returned on failure, otherwize pointer to a buffer holding
+ * text representation is returned, caller must run free(3) for the
+ * pointer.
+ */
+static char *
+generate_loop_for_req(strnodelist *stack_tail, provnode *head,
+    filenode *fnode)
+{
+	provnode *pnode;
+	strnodelist *stack_ptr, *loop_entry;
+	char *buf, **revstack;
+	size_t bufsize;
+	int i, stack_depth;
+
+	loop_entry = NULL;
+	/* fast forward stack to the component that is required now */
+	for (pnode = head->next; pnode; pnode = pnode->next) {
+		if (loop_entry)
+			break;
+		stack_depth = 0;
+		for (stack_ptr = stack_tail; stack_ptr;
+		    stack_ptr = stack_ptr->next) {
+			stack_depth++;
+			if (stack_ptr->node == pnode->fnode) {
+				loop_entry = stack_ptr;
+				break;
+			}
+		}
+	}
+
+	if (loop_entry == NULL)
+		return (NULL);
+
+	stack_depth += 2; /* fnode + loop_entry */
+	revstack = emalloc(sizeof(char *) * stack_depth);
+	bzero(revstack, (sizeof(char *) * stack_depth));
+
+	/* reverse stack and estimate buffer size to allocate */
+	bufsize = 1; /* tralining \0 */
+
+	revstack[stack_depth - 1] = loop_entry->node->filename;
+	bufsize += strlen(revstack[stack_depth - 1]);
+
+	revstack[stack_depth - 2] = fnode->filename;
+	bufsize += strlen(revstack[stack_depth - 2]);
+	fnode->issues_count++;
+
+	stack_ptr = stack_tail;
+	for (i = stack_depth - 3; i >= 0; i--) {
+		revstack[i] = stack_ptr->node->filename;
+		stack_ptr->node->issues_count++;
+		stack_ptr = stack_ptr->next;
+		bufsize += strlen(revstack[i]);
+	}
+	bufsize += strlen(" -> ") * (stack_depth - 1);
+
+	buf = emalloc(bufsize);
+	bzero(buf, bufsize);
+
+	for (i = 0; i < stack_depth; i++) {
+		strlcat(buf, revstack[i], bufsize);
+		if (i < stack_depth - 1)
+			strlcat(buf, " -> ", bufsize);
+	}
+
+	free(revstack);
+	return (buf);
+}
+/*
  * below are the functions that traverse the graphs we have built
  * finding out the desired ordering, printing each file in turn.
  * if missing requirements, or cyclic graphs are detected, a
@@ -621,17 +785,22 @@ crunch_all_files(void)
  * provision.
  */
 static void
-satisfy_req(f_reqnode *rnode, char *filename)
+satisfy_req(f_reqnode *rnode, filenode *fnode, strnodelist *stack_ptr)
 {
 	Hash_Entry *entry;
 	provnode *head;
+	strnodelist stack_item;
+	char *buf;
 
 	entry = rnode->entry;
 	head = Hash_GetValue(entry);
 
+	if (do_graphviz == true)
+		generate_graphviz_file_links(entry, fnode);
+
 	if (head == NULL) {
 		warnx("requirement `%s' in file `%s' has no providers.",
-		    Hash_GetKey(entry), filename);
+		    Hash_GetKey(entry), fnode->filename);
 		exit_code = 1;
 		return;
 	}
@@ -645,20 +814,34 @@ satisfy_req(f_reqnode *rnode, char *filename)
 	 *	print that there is a circular dependency on it and abort
 	 */
 	if (head->in_progress == SET) {
-		warnx("Circular dependency on provision `%s' in file `%s'.",
-		    Hash_GetKey(entry), filename);
 		exit_code = 1;
+		buf = generate_loop_for_req(stack_ptr, head,
+		    fnode);
+
+		if (buf == NULL) {
+			warnx("Circular dependency on provision `%s' in "
+			    "file `%s' (tracing has failed).",
+			    Hash_GetKey(entry), fnode->filename);
+			return;
+		}
+
+		warnx("Circular dependency on provision `%s': %s.",
+		    Hash_GetKey(entry), buf);
+		free(buf);
 		return;
 	}
 
 	head->in_progress = SET;
 	
+	stack_item.next = stack_ptr;
+	stack_item.node = fnode;
+
 	/*
 	 * while provision_list is not empty
 	 *	do_file(first_member_of(provision_list));
 	 */
 	while (head->next != NULL)
-		do_file(head->next->fnode);
+		do_file(head->next->fnode, &stack_item);
 }
 
 static int
@@ -701,12 +884,13 @@ keep_ok(filenode *fnode)
  * Circular dependencies will cause problems if we do.
  */
 static void
-do_file(filenode *fnode)
+do_file(filenode *fnode, strnodelist *stack_ptr)
 {
 	f_reqnode *r;
 	f_provnode *p, *p_tmp;
-	provnode *pnode;
+	provnode *pnode, *head;
 	int was_set;	
+	char *dep_name;
 
 	DPRINTF((stderr, "do_file on %s.\n", fnode->filename));
 
@@ -729,18 +913,44 @@ do_file(filenode *fnode)
 	 *	satisfy_req(r, filename)
 	 */
 	r = fnode->req_list;
+	fnode->sequence = 0;
 	while (r != NULL) {
-		satisfy_req(r, fnode->filename);
+		satisfy_req(r, fnode, stack_ptr);
+		/* find sequence number where all requirements are satisfied */
+		head = Hash_GetValue(r->entry);
+		if (head && head->sequence > fnode->sequence)
+			fnode->sequence = head->sequence;
 		r = r->next;
 	}
 	fnode->req_list = NULL;
+	fnode->sequence++;
 
+	/* if we've seen issues with this file - put it to the tail */
+	if (fnode->issues_count)
+		fnode->sequence = max_sequence + 1;
+
+	if (max_sequence < fnode->sequence)
+		max_sequence = fnode->sequence;
+
 	/*
 	 * for each provision of fnode -> p
 	 *	remove fnode from provision list for p in hash table
 	 */
 	p = fnode->prov_list;
 	while (p != NULL) {
+		/* mark all troublemakers on graphviz */
+		if (do_graphviz == true && fnode->issues_count) {
+			dep_name = Hash_GetKey(p->entry);
+			if (!is_fake_prov(dep_name))
+				printf("\"%s\" [ color=red, penwidth=4 ];\n",
+				    dep_name);
+		}
+
+		/* update sequence when provided requirements are satisfied */
+		head = Hash_GetValue(p->entry);
+		if (head->sequence < fnode->sequence)
+			head->sequence = fnode->sequence;
+
 		p_tmp = p;
 		pnode = p->pnode;
 		if (pnode->next != NULL) {
@@ -759,8 +969,11 @@ do_file(filenode *fnode)
 	DPRINTF((stderr, "next do: "));
 
 	/* if we were already in progress, don't print again */
-	if (was_set == 0 && skip_ok(fnode) && keep_ok(fnode))
-		printf("%s\n", fnode->filename);
+	if (do_graphviz != true && was_set == 0 && skip_ok(fnode) &&
+	    keep_ok(fnode)) {
+		*fn_seqlist = fnode;
+		fn_seqlist++;
+	}
 	
 	if (fnode->next != NULL) {
 		fnode->next->last = fnode->last;
@@ -769,19 +982,103 @@ do_file(filenode *fnode)
 		fnode->last->next = fnode->next;
 	}
 
+	if (fnode->issues_count)
+		warnx("`%s' was seen in circular dependencies for %d times.",
+		    fnode->filename, fnode->issues_count);
+
 	DPRINTF((stderr, "nuking %s\n", fnode->filename));
-#if 0
-	if (was_set == 0) {
-   		free(fnode->filename);
-   		free(fnode);
-	}
-#endif
 }
 
 static void
+generate_graphviz_header()
+{
+
+	if (do_graphviz != true)
+		return;
+
+	printf("digraph rcorder {\n"
+	    "rankdir=\"BT\";\n"
+	    "node [style=rounded, shape=record];\n"
+	    "graph [overlap = false];\n");
+}
+
+static void
+generate_graphviz_footer()
+{
+
+	if (do_graphviz == true)
+		printf("}\n");
+}
+
+static void
+generate_graphviz_providers()
+{
+	Hash_Entry *entry;
+	Hash_Search psearch;
+	provnode *head, *pnode;
+	char *dep_name;
+
+	if (do_graphviz != true)
+		return;
+
+	entry = Hash_EnumFirst(provide_hash, &psearch);
+	if (entry == NULL)
+		return;
+
+	do {
+		dep_name = Hash_GetKey(entry);
+		if (is_fake_prov(dep_name))
+			continue;
+		head = Hash_GetValue(entry);
+		/* no providers for this requirement */
+		if (head == NULL || head->next == NULL) {
+			printf("\"%s\" [label=\"{ %s | ENOENT }\", "
+			    "style=\"rounded,filled\", color=red];\n",
+			    dep_name, dep_name);
+			continue;
+		}
+		/* one PROVIDE word for one file that matches */
+		if (head->next->next == NULL &&
+		    strcmp(dep_name,
+		    basename(head->next->fnode->filename)) == 0) {
+		        continue;
+		}
+		printf("\"%s\" [label=\"{ %s | ", dep_name, dep_name);
+		for (pnode = head->next; pnode; pnode = pnode->next)
+			printf("%s\\n", basename(pnode->fnode->filename));
+
+		printf("}\"];\n");
+	} while (NULL != (entry = Hash_EnumNext(&psearch)));
+}
+
+static int
+sequence_cmp(const void *a, const void *b)
+{
+	const filenode *fna = *((const filenode * const *)a);
+	const filenode *fnb = *((const filenode * const *)b);
+	int left, right;
+
+	/* push phantom files to the end */
+	if (fna == NULL || fnb == NULL)
+		return ((fna < fnb) - (fna > fnb));
+
+	left =  fna->sequence;
+	right = fnb->sequence;
+
+	return ((left > right) - (left < right));
+}
+
+static void
 generate_ordering(void)
 {
+	filenode **seqlist, **psl;
+	int last_seq = 0;
 
+	/* Prepare order buffer, use an additional one as a list terminator */
+	seqlist = emalloc(sizeof(filenode *) * (file_count + 1));
+	bzero(seqlist, sizeof(filenode *) * (file_count + 1));
+	fn_seqlist = seqlist;
+
 	/*
 	 * while there remain undone files{f},
 	 *	pick an arbitrary f, and do_file(f)
@@ -798,6 +1095,24 @@ generate_ordering(void)
 	 */
 	while (fn_head->next != NULL) {
 		DPRINTF((stderr, "generate on %s\n", fn_head->next->filename));
-		do_file(fn_head->next);
+		do_file(fn_head->next, NULL);
 	}
+
+	/* Sort filenode list based on sequence */
+	qsort(seqlist, file_count, sizeof(filenode *), sequence_cmp);
+
+	for (psl = seqlist; *psl; psl++) {
+		printf("%s%s",
+		    (last_seq == 0 ? "" :
+		    (do_parallel != true || last_seq != (*psl)->sequence) ?
+		    "\n" : " "),
+		(*psl)->filename);
+		last_seq = (*psl)->sequence;
+		free((*psl)->filename);
+		free(*psl);
+	}
+	if (last_seq)
+		printf("\n");
+
+	free(seqlist);
 }


More information about the svn-src-all mailing list