public inbox for gentoo-commits@lists.gentoo.org
 help / color / mirror / Atom feed
* [gentoo-commits] proj/portage-utils:master commit in: /, tests/qlop/, man/include/, man/
@ 2019-02-27 20:53 Fabian Groffen
  0 siblings, 0 replies; only message in thread
From: Fabian Groffen @ 2019-02-27 20:53 UTC (permalink / raw
  To: gentoo-commits

commit:     c74a5abf3fd8db03adb531f95ecff5316d997ab3
Author:     Fabian Groffen <grobian <AT> gentoo <DOT> org>
AuthorDate: Wed Feb 27 20:41:45 2019 +0000
Commit:     Fabian Groffen <grobian <AT> gentoo <DOT> org>
CommitDate: Wed Feb 27 20:50:21 2019 +0000
URL:        https://gitweb.gentoo.org/proj/portage-utils.git/commit/?id=c74a5abf

qlop: rewrite from scratch

This new implementation achieves a few things:
- unified code path for all modes of operation (thus consistent results)
- more flexibility to implement new features
- simplification of the codebase

Short version of what this commit changes:
- existing flags -t -g have been replaced with -a and -t
- -c has been been replaced with -r and no longer uses proc processing
  code (thus works everywhere)
- addition of an ETA for running emerges (subject to improvements)
- allow reading a file of atoms (e.g. /var/lib/portage/world)
- summary mode -c to compute grand total, e.g. to compute world compile
  time

Bug: https://bugs.gentoo.org/161244
Bug: https://bugs.gentoo.org/603024
Bug: https://bugs.gentoo.org/636334
Bug: https://bugs.gentoo.org/658824
Signed-off-by: Fabian Groffen <grobian <AT> gentoo.org>

 man/include/qlop.desc         |   25 +-
 man/include/qlop.optdesc.yaml |   23 +-
 man/qlop.1                    |   66 +-
 qlop.c                        | 1479 +++++++++++++++++++++--------------------
 tests/qlop/dotest             |   20 +-
 tests/qlop/list01.good        |    4 +-
 tests/qlop/list02.good        |    6 +-
 tests/qlop/list03.good        |    8 +-
 tests/qlop/list04.good        |    2 +-
 tests/qlop/list05.good        |    2 +-
 tests/qlop/list06.good        |    4 +-
 tests/qlop/list07.good        |    4 +-
 tests/qlop/list08.good        |    4 +-
 tests/qlop/list09.good        |    6 +-
 14 files changed, 892 insertions(+), 761 deletions(-)

diff --git a/man/include/qlop.desc b/man/include/qlop.desc
index d99cc94..0c14e00 100644
--- a/man/include/qlop.desc
+++ b/man/include/qlop.desc
@@ -1,4 +1,25 @@
 .I qlop
 reads from $EMERGE_LOG_DIR/emerge.log and tries to extract
-information about merges, unmerges and syncs.  For packages, it can
-calculate average merge times or just list them.
+information about merges, unmerges and syncs.  It can
+calculate average merge times or just list them.  When given no
+arguments or just \fB-v\fR, \fIqlop\fR acts as if \fB-must\fR was given
+and thus lists the time taken for all occurrances of merges, unmerges
+and sync operations found in the log.
+.P
+By default, packages are printed as CATEGORY/PN.  Use \fB-v\fR to print
+the package version and revision numbers, e.g\. CATEGORY/PF.  Note that
+when using this option, averages (\fB-a\fR) are reported per version
+instead of per package.
+.P
+The non-option arguments to \fIqlop\fR can be any valid atoms.  Multiple
+can be given to match.  Since all of these are treated as atoms, version
+specifications can be used such as \fB<bash-5\fR.  This allows to zoom
+in further on specific packages.
+.P
+After version \fB0.74\fR of portage-utils, \fIqlop\fR was changed
+considerably to be more consistent and more advanced.  Most notably,
+this has changed default date output and commmand line flags.  Instead
+of reporting the time the operation finished, \fIqlop\fR now reports the
+time the operation started.  The behaviour of the old \fB-q\fR flag is
+best matched by the new \fB-t\fR flag.  Similar, the old \fB-t\fR flag
+is matched by the new \fB-a\fR flag.

diff --git a/man/include/qlop.optdesc.yaml b/man/include/qlop.optdesc.yaml
index 3ff62c3..a8fae61 100644
--- a/man/include/qlop.optdesc.yaml
+++ b/man/include/qlop.optdesc.yaml
@@ -18,18 +18,27 @@ date: |
     .IP FORMAT|DATE
     Use \fIFORMAT\fR as input for \fBstrptime\fR(3) to parse \fIDATE\fR.
     .RE
-gauge: |
-    Gauge number of times a package has been merged.  This shows the
-    merge time for each individual merge of package.
 time: |
-    Calculate merge time for a specific package.  This is the average
-    time for all merges of package.
+    Show the time it took to merge, unmerge or sync.
+average: |
+    Calculate average merge, unmerge or sync time.  This is the average
+    time for all occurrences found respecting any date limits.
+summary: |
+    Show a grand total for averages.  This option implies \fB-a\fR.
+    Useful to compute time it takes to recompile all packages or a set
+    of packages (e.g\. world).
 human: |
-    Print seconds in human readable format (needs \fB\-t\fR), using
+    Print elapssed time in human readable format.  This form uses
     minutes, hours and days instead of just seconds.
 current: |
     Show current emerging packages.  This relies on
     .I FEATURES=sandbox
     in order to detect running merges.
 verbose: |
-    Print package versions and revisions.
+    Print package versions and revisions (PF) instead of package (PN).
+running: |
+    Print operations currently in progress.  An ETA is calculated based
+    on the average for the operation.  If the elapsed exceeds the
+    average, the ETA is calculated against the longest time observed for
+    the operation.  If the elapsed time exceeds this too, or no previous
+    occurrences for the operation exist, \fIunknown\fR is printed.

diff --git a/man/qlop.1 b/man/qlop.1
index bdfe3af..46a6f94 100644
--- a/man/qlop.1
+++ b/man/qlop.1
@@ -8,35 +8,68 @@ qlop \- emerge log analyzer
 .SH DESCRIPTION
 .I qlop
 reads from $EMERGE_LOG_DIR/emerge.log and tries to extract
-information about merges, unmerges and syncs.  For packages, it can
-calculate average merge times or just list them.
+information about merges, unmerges and syncs.  It can
+calculate average merge times or just list them.  When given no
+arguments or just \fB-v\fR, \fIqlop\fR acts as if \fB-must\fR was given
+and thus lists the time taken for all occurrances of merges, unmerges
+and sync operations found in the log.
+.P
+By default, packages are printed as CATEGORY/PN.  Use \fB-v\fR to print
+the package version and revision numbers, e.g\. CATEGORY/PF.  Note that
+when using this option, averages (\fB-a\fR) are reported per version
+instead of per package.
+.P
+The non-option arguments to \fIqlop\fR can be any valid atoms.  Multiple
+can be given to match.  Since all of these are treated as atoms, version
+specifications can be used such as \fB<bash-5\fR.  This allows to zoom
+in further on specific packages.
+.P
+After version \fB0.74\fR of portage-utils, \fIqlop\fR was changed
+considerably to be more consistent and more advanced.  Most notably,
+this has changed default date output and commmand line flags.  Instead
+of reporting the time the operation finished, \fIqlop\fR now reports the
+time the operation started.  The behaviour of the old \fB-q\fR flag is
+best matched by the new \fB-t\fR flag.  Similar, the old \fB-t\fR flag
+is matched by the new \fB-a\fR flag.
 .SH OPTIONS
 .TP
-\fB\-g\fR, \fB\-\-gauge\fR
-Gauge number of times a package has been merged.  This shows the
-merge time for each individual merge of package.
+\fB\-c\fR, \fB\-\-summary\fR
+Show a grand total for averages.  This option implies \fB-a\fR.
+Useful to compute time it takes to recompile all packages or a set
+of packages (e.g\. world).
 .TP
 \fB\-t\fR, \fB\-\-time\fR
-Calculate merge time for a specific package.  This is the average
-time for all merges of package.
+Show the time it took to merge, unmerge or sync.
+.TP
+\fB\-a\fR, \fB\-\-average\fR
+Calculate average merge, unmerge or sync time.  This is the average
+time for all occurrences found respecting any date limits.
 .TP
 \fB\-H\fR, \fB\-\-human\fR
-Print seconds in human readable format (needs \fB\-t\fR), using
+Print elaspsed time in human readable format.  This form uses
 minutes, hours and days instead of just seconds.
 .TP
-\fB\-l\fR, \fB\-\-list\fR
+\fB\-m\fR, \fB\-\-merge\fR
 Show merge history.
 .TP
-\fB\-u\fR, \fB\-\-unlist\fR
+\fB\-u\fR, \fB\-\-unmerge\fR
 Show unmerge history.
 .TP
+\fB\-U\fR, \fB\-\-autoclean\fR
+Show autoclean unmerge history.
+.TP
 \fB\-s\fR, \fB\-\-sync\fR
 Show sync history.
 .TP
-\fB\-c\fR, \fB\-\-current\fR
-Show current emerging packages.  This relies on
-.I FEATURES=sandbox
-in order to detect running merges.
+\fB\-e\fR, \fB\-\-endtime\fR
+Report time at which the operation finished (iso started).
+.TP
+\fB\-r\fR, \fB\-\-running\fR
+Print operations currently in progress.  An ETA is calculated based
+on the average for the operation.  If the elapsed exceeds the
+average, the ETA is calculated against the longest time observed for
+the operation.  If the elapsed time exceeds this too, or no previous
+occurrences for the operation exist, \fIunknown\fR is printed.
 .TP
 \fB\-d\fR \fI<arg>\fR, \fB\-\-date\fR \fI<arg>\fR
 Limit the selection of packages to the date given, or to the range
@@ -62,11 +95,14 @@ Use \fIFORMAT\fR as input for \fBstrptime\fR(3) to parse \fIDATE\fR.
 \fB\-f\fR \fI<arg>\fR, \fB\-\-logfile\fR \fI<arg>\fR
 Read emerge logfile instead of $EMERGE_LOG_DIR/emerge.log.
 .TP
+\fB\-w\fR \fI<arg>\fR, \fB\-\-atoms\fR \fI<arg>\fR
+Read package atoms to report from file.
+.TP
 \fB\-\-root\fR \fI<arg>\fR
 Set the ROOT env var.
 .TP
 \fB\-v\fR, \fB\-\-verbose\fR
-Print package versions and revisions.
+Print package versions and revisions (PF) instead of package (PN).
 .TP
 \fB\-q\fR, \fB\-\-quiet\fR
 Tighter output; suppress warnings.

diff --git a/qlop.c b/qlop.c
index a042fa7..26b27b2 100644
--- a/qlop.c
+++ b/qlop.c
@@ -1,38 +1,47 @@
 /*
- * Copyright 2005-2018 Gentoo Foundation
+ * Copyright 2005-2019 Gentoo Foundation
  * Distributed under the terms of the GNU General Public License v2
  *
  * Copyright 2005-2010 Ned Ludd        - <solar@gentoo.org>
  * Copyright 2005-2014 Mike Frysinger  - <vapier@gentoo.org>
+ * Copyright 2018-     Fabian Groffen  - <grobian@gentoo.org>
  */
 
 #ifdef APPLET_qlop
 
 #define QLOP_DEFAULT_LOGFILE "emerge.log"
 
-#define QLOP_FLAGS "gtHluscd:f:" COMMON_FLAGS
+#define QLOP_FLAGS "ctaHmuUserd:f:w:" COMMON_FLAGS
 static struct option const qlop_long_opts[] = {
-	{"gauge",     no_argument, NULL, 'g'},
+	{"summary",   no_argument, NULL, 'c'},
 	{"time",      no_argument, NULL, 't'},
+	{"average",   no_argument, NULL, 'a'},
 	{"human",     no_argument, NULL, 'H'},
-	{"list",      no_argument, NULL, 'l'},
-	{"unlist",    no_argument, NULL, 'u'},
+	{"merge",     no_argument, NULL, 'm'},
+	{"unmerge",   no_argument, NULL, 'u'},
+	{"autoclean", no_argument, NULL, 'U'},
 	{"sync",      no_argument, NULL, 's'},
-	{"current",   no_argument, NULL, 'c'},
+	{"endtime",   no_argument, NULL, 'e'},
+	{"running",   no_argument, NULL, 'r'},
 	{"date",       a_argument, NULL, 'd'},
 	{"logfile",    a_argument, NULL, 'f'},
+	{"atoms",      a_argument, NULL, 'w'},
 	COMMON_LONG_OPTS
 };
 static const char * const qlop_opts_help[] = {
-	"Gauge number of times a package has been merged",
-	"Calculate merge time for a specific package",
-	"Print seconds in human readable format (needs -t)",
+	"Print summary of average merges (implies -a)",
+	"Print time taken to complete action",
+	"Print average time taken to complete action",
+	"Print elapsed time in human readable format (use with -t or -a)",
 	"Show merge history",
 	"Show unmerge history",
+	"Show autoclean unmerge history",
 	"Show sync history",
+	"Report time at which the operation finished (iso started)",
 	"Show current emerging packages",
 	"Limit selection to this time (1st -d is start, 2nd -d is end)",
 	"Read emerge logfile instead of $EMERGE_LOG_DIR/" QLOP_DEFAULT_LOGFILE,
+	"Read package atoms to report from file",
 	COMMON_OPTS_HELP
 };
 static const char qlop_desc[] =
@@ -44,672 +53,18 @@ static const char qlop_desc[] =
 	"  -d '%d.%m.%Y|25.12.2015'  (format is specified)";
 #define qlop_usage(ret) usage(ret, QLOP_FLAGS, qlop_long_opts, qlop_opts_help, qlop_desc, lookup_applet_idx("qlop"))
 
-#define QLOP_LIST    0x01
-#define QLOP_UNLIST  0x02
-
-static void
-print_seconds_for_earthlings(const unsigned long t)
-{
-	unsigned dd, hh, mm, ss;
-	unsigned long tt = t;
-	ss = tt % 60; tt /= 60;
-	mm = tt % 60; tt /= 60;
-	hh = tt % 24; tt /= 24;
-	dd = tt;
-	if (dd) printf("%s%u%s day%s, ", GREEN, dd, NORM, (dd == 1 ? "" : "s"));
-	if (hh) printf("%s%u%s hour%s, ", GREEN, hh, NORM, (hh == 1 ? "" : "s"));
-	if (mm) printf("%s%u%s minute%s, ", GREEN, mm, NORM, (mm == 1 ? "" : "s"));
-	printf("%s%u%s second%s", GREEN, ss, NORM, (ss == 1 ? "" : "s"));
-}
-
-static const char *
-chop_ctime(time_t t)
-{
-	static char ctime_out[50];
-	int ret = snprintf(ctime_out, sizeof(ctime_out), "%s", ctime(&t));
-	/* Assume no error! */
-	ctime_out[ret - 1] = '\0';
-	return ctime_out;
-}
-
-static unsigned long
-show_merge_times(char *package, const char *logfile, int average, char human_readable,
-                 time_t start_time, time_t end_time)
-{
-	FILE *fp;
-	char cat[126], buf[2][BUFSIZ];
-	char *pkg, *p, *q;
-	char ep[BUFSIZ];
-	unsigned long count, merge_time;
-	time_t t[2];
-	depend_atom *atom;
-	unsigned int parallel_emerge;
-
-	t[0] = t[1] = 0UL;
-	count = merge_time = 0;
-	cat[0] = 0;
-
-	/* setup cat and pkg vars */
-	if ((p = strchr(package, '/')) != NULL) {
-		pkg = p + 1;
-		strncpy(cat, package, sizeof(cat));
-		if ((p = strchr(cat, '/')) != NULL)
-			*p = 0;
-	} else {
-		pkg = package;
-	}
-
-	if ((fp = fopen(logfile, "r")) == NULL) {
-		warnp("Could not open logfile '%s'", logfile);
-		return 1;
-	}
-
-	/* loop over lines searching for cat/pkg */
-	parallel_emerge = 0;
-	while (fgets(buf[0], sizeof(buf[0]), fp) != NULL) {
-		if ((p = strchr(buf[0], '\n')) != NULL)
-			*p = '\0';
-		if ((p = strchr(buf[0], ':')) == NULL)
-			continue;
-		*p++ = '\0';
-		if (strstr(p, pkg) == NULL)
-			continue;
-
-		t[0] = atol(buf[0]);
-		if (t[0] < start_time || t[0] > end_time)
-			continue;
-
-		/* copy message (stripping timestamp) */
-		strncpy(buf[1], p, BUFSIZ);
-		rmspace(buf[1]);
-
-		if (strncmp(buf[1], "Started emerge on:", 18) == 0) {
-			/* a parallel emerge was launched */
-			parallel_emerge++;
-			continue;
-		}
-
-		if (strncmp(buf[1], "*** terminating.", 16) == 0) {
-			if (parallel_emerge > 0) {
-				/* a parallel emerge has finished */
-				parallel_emerge--;
-				continue;
-			} else {
-				/* the main emerge was stopped? if there's more lines
-				 * this file is just corrupt or truncated at the front */
-				continue;
-			}
-		}
-
-		if (strncmp(buf[1], ">>> emerge (", 12) == 0) {
-			/* construct the matching end marker */
-			snprintf(ep, BUFSIZ, "completed %s", &buf[1][4]);
-
-			/* skip over "(X of Y)" */
-			if ((p = strchr(buf[1], ')')) == NULL) {
-				*ep = '\0';
-				continue;
-			}
-			*p++ = '\0';
-
-			/* get the package as atom */
-			strncpy(buf[0], p, BUFSIZ);
-			rmspace(buf[0]);
-			if ((p = strchr(buf[0], ' ')) == NULL) {
-				*ep = '\0';
-				continue;
-			}
-			*p = '\0';
-			if ((atom = atom_explode(buf[0])) == NULL) {
-				*ep = '\0';
-				continue;
-			}
-
-			/* match atom against our search */
-			if ((*cat && ((strcmp(cat, atom->CATEGORY) == 0) &&
-							(strcmp(pkg, atom->PN) == 0))) ||
-					(strcmp(pkg, atom->PN) == 0))
-			{
-				while (fgets(buf[0], sizeof(buf[0]), fp) != NULL) {
-					if ((p = strchr(buf[0], '\n')) != NULL)
-						*p = '\0';
-					if ((p = strchr(buf[0], ':')) == NULL)
-						continue;
-					*p++ = '\0';
-
-					t[1] = atol(buf[0]);
-					strcpy(buf[1], p);
-					rmspace(buf[1]);
-
-					if (strncmp(buf[1], "Started emerge on:", 18) == 0) {
-						/* a parallel emerge was launched */
-						parallel_emerge++;
-						continue;
-					}
-
-					if (strncmp(buf[1], "*** terminating.", 16) == 0) {
-						if (parallel_emerge > 0) {
-							/* a parallel emerge has finished */
-							parallel_emerge--;
-							continue;
-						} else {
-							/* the main emerge was stopped? if there's
-							 * more lines this file is just corrupt or
-							 * truncated at the front */
-							break;
-						}
-					}
-
-					/* pay attention to malformed log files (when the
-					 * end of an emerge process is not indicated by the
-					 * line '*** terminating'). We assume that the log
-					 * is malformed when we find a parallel emerge
-					 * process which is trying to emerge the same
-					 * package
-					 */
-					if (strncmp(buf[1], ">>> emerge (", 12) == 0 &&
-							parallel_emerge > 0)
-					{
-						/* find package name */
-						p = strchr(buf[1], ')');
-						q = strchr(ep, ')');
-						if (!p || !q)
-							continue;
-
-						/* is this emerge doing the same thing as we're
-						 * looking for? that means we failed */
-						if (strcmp(p, q) == 0) {
-							parallel_emerge--;
-							/* update the main emerge reference data */
-							snprintf(ep, BUFSIZ, "completed %s", &buf[1][4]);
-							t[0] = t[1];
-							continue;
-						}
-					}
-
-					/* if this line matches "completed emerge (X of Y) ..."
-					 * we're finally somewhere */
-					if (strncmp(&buf[1][4], ep, BUFSIZ - 4) == 0) {
-						if (!average) {
-							buf[1][0] = '\0';
-							if (verbose) {
-								if (atom->PR_int)
-									snprintf(buf[1], sizeof(buf[1]),
-											"-%s-r%i", atom->PV,  atom->PR_int);
-								else
-									snprintf(buf[1], sizeof(buf[1]),
-											"-%s", atom->PV);
-							}
-							printf("%s%s%s%s: %s: ",
-									BLUE, atom->PN, buf[1], NORM,
-									chop_ctime(t[0]));
-							if (human_readable)
-								print_seconds_for_earthlings(t[1] - t[0]);
-							else
-								printf("%s%"PRIu64"%s seconds",
-										GREEN, (uint64_t)(t[1] - t[0]), NORM);
-							printf("\n");
-						}
-						merge_time += (t[1] - t[0]);
-						count++;
-						break;
-					}
-				}
-			}
-			atom_implode(atom);
-		}
-	}
-	fclose(fp);
-	if (count == 0)
-		return 0;
-	if (average == 1) {
-		printf("%s%s%s: ", BLUE, pkg, NORM);
-		if (human_readable)
-			print_seconds_for_earthlings(merge_time / count);
-		else
-			printf("%s%lu%s seconds average", GREEN, merge_time / count, NORM);
-		printf(" for %s%lu%s merges\n", GREEN, count, NORM);
-	} else {
-		printf("%s%s%s: %s%lu%s times\n", BLUE, pkg, NORM, GREEN, count, NORM);
-	}
-	return 0;
-}
-
-static void
-show_emerge_history(int listflag, array_t *atoms, const char *logfile,
-                    time_t start_time, time_t end_time)
-{
-	FILE *fp;
-	int linelen;
-	size_t buflen;
-	char *buf, merged;
-	char *p, *q;
-	bool showit;
-	size_t i;
-	time_t t;
-	depend_atom *atom, *logatom;
-
-	if ((fp = fopen(logfile, "r")) == NULL) {
-		warnp("Could not open logfile '%s'", logfile);
-		return;
-	}
-
-	buf = NULL;
-	while ((linelen = getline(&buf, &buflen, fp)) >= 0) {
-		if (linelen < 30)
-			continue;
-
-		rmspace_len(buf, (size_t)linelen);
-		if ((p = strchr(buf, ':')) == NULL)
-			continue;
-		*p = 0;
-		q = p + 3;
-		/* Make sure there's leading white space and not a truncated
-		 * string. #573106 */
-		if (p[1] != ' ' || p[2] != ' ')
-			continue;
-
-		t = (time_t) atol(buf);
-		if (t < start_time || t > end_time)
-			continue;
-
-		if ((listflag & QLOP_LIST) && !strncmp(q, "::: completed emerge (", 22)) {
-			merged = 1;
-			if ((p = strchr(q, ')')) == NULL)
-				continue;
-			q = p + 2;
-			if ((p = strchr(q, ' ')) == NULL)
-				continue;
-			*p = 0;
-		} else if ((listflag & QLOP_UNLIST) && !strncmp(q, ">>> unmerge success: ", 21)) {
-			merged = 0;
-			if ((p = strchr(q, ':')) == NULL)
-				continue;
-			q = p + 2;
-		} else
-			continue;
-
-		logatom = atom_explode(q);
-		if (array_cnt(atoms)) {
-			showit = false;
-			array_for_each(atoms, i, atom)
-				if (atom_compare(atom, logatom) == EQUAL) {
-					showit = true;
-					break;
-				}
-		} else
-			showit = true;
-
-		if (showit) {
-			if (!quiet)
-				printf("%s %s %s%s%s\n", chop_ctime(t), (merged ? ">>>" : "<<<"), (merged ? GREEN : RED), q, NORM);
-			else {
-				if (quiet == 1)
-					printf("%s ", chop_ctime(t));
-				if (quiet <= 2)
-					printf("%s ", (merged ? ">>>" : "<<<"));
-				printf("%s%s/%s%s\n", (merged ? GREEN : RED), logatom->CATEGORY, logatom->PN, NORM);
-			}
-		}
-		atom_implode(logatom);
-	}
-
-	free(buf);
-	fclose(fp);
-}
-
-/* The format of the sync log has changed over time.
-
-Old format:
-1106804103: Started emerge on: Jan 27, 2005 05:35:03
-1106804103:  *** emerge  sync
-1106804103:  === sync
-1106804103: >>> starting rsync with rsync://192.168.0.5/gentoo-portage
-1106804537: === Sync completed with rsync://192.168.0.5/gentoo-portage
-1106804538:  *** terminating.
-
-New format:
-1431764402: Started emerge on: May 16, 2015 04:20:01
-1431764402:  *** emerge --quiet --keep-going --verbose --nospinner --oneshot --quiet-build=n --sync
-1431764402:  === sync
-1431764402: >>> Syncing repository 'gentoo' into '/usr/portage'...
-1431764402: >>> Starting rsync with rsync://[2a01:90:200:10::1a]/gentoo-portage
-1431764460: === Sync completed for gentoo
-1431764493:  *** terminating.
-*/
-static void
-show_sync_history(const char *logfile, time_t start_time, time_t end_time)
-{
-	FILE *fp;
-	int linelen;
-	size_t buflen;
-	char *buf, *p;
-	time_t t;
-
-	if ((fp = fopen(logfile, "r")) == NULL) {
-		warnp("Could not open logfile '%s'", logfile);
-		return;
-	}
-
-	buf = NULL;
-	/* Just find the finish lines. */
-	while ((linelen = getline(&buf, &buflen, fp)) >= 0) {
-		/* This cuts out like ~10% of the log. */
-		if (linelen < 35)
-			continue;
-
-		/* Make sure there's a timestamp in here. */
-		if ((p = strchr(buf, ':')) == NULL)
-			continue;
-		p += 2;
-
-		if (strncmp(p, "=== Sync completed ", 19) != 0)
-			continue;
-		p += 19;
-
-		rmspace_len(buf, (size_t)linelen);
-
-		t = (time_t)atol(buf);
-		if (t < start_time || t > end_time)
-			continue;
-
-		if (!strncmp(p, "with ", 5))
-			p += 5;
-		else if (!strncmp(p, "for ", 4))
-			/* This shows just the repo name not the remote host ... */
-			p += 4;
-		else
-			continue;
-		printf("%s >>> %s%s%s\n", chop_ctime(t), GREEN, p, NORM);
-	}
-
-	free(buf);
-	fclose(fp);
-}
-
-static void show_current_emerge(void);
-#ifdef __linux__
-# include <asm/param.h>
-#endif
-#if defined __linux__ || defined __GNU__
-# include <elf.h>
-static unsigned long hz = 0;
-static void init_hz(void)
-{
-#ifdef HZ
-	hz = HZ;
-#endif
-	/* kernel pushes elf notes onto stack */
-	unsigned long *elf_note = (unsigned long *)environ;
-	while (!*elf_note++)
-		continue;
-	while (elf_note[0]) {
-		if (elf_note[0] == AT_CLKTCK) {
-			hz = elf_note[1];
-			break;
-		}
-		elf_note += 2;
-	}
-	if (!hz)
-		hz = 100;
-}
-
-static char *
-root_readlink(const int pid)
-{
-	static char path[_Q_PATH_MAX];
-	char buf[_Q_PATH_MAX];
-	memset(&path, 0, sizeof(path));
-	snprintf(buf, sizeof(buf), "/proc/%d/root", pid);
-	if (readlink(buf, path, sizeof(path) - 1) == -1)
-		return NULL;
-	else
-		return path;
-}
-
-void show_current_emerge(void)
-{
-	DIR *proc;
-	struct dirent *de;
-	pid_t pid;
-	static char *cmdline, *bufstat;
-	static size_t cmdline_len, bufstat_len;
-	char path[50];
-	char *p, *q;
-	unsigned long long start_time = 0;
-	double uptime_secs;
-	time_t start_date;
-
-	if ((proc = opendir("/proc")) == NULL) {
-		warnp("Could not open /proc");
-		return;
-	}
-
-	if (!hz)
-		init_hz();
-
-	while ((de = readdir(proc)) != NULL) {
-
-		if ((pid = (pid_t)atol(de->d_name)) == 0)
-			continue;
-
-		/* portage renames the cmdline so the package name is first */
-		snprintf(path, sizeof(path), "/proc/%i/cmdline", pid);
-		if (!eat_file(path, &cmdline, &cmdline_len))
-			continue;
-
-		if (cmdline[0] == '[' && (p = strchr(cmdline, ']')) != NULL &&
-				strstr(cmdline, "sandbox") != NULL)
-		{
-			*p = '\0';
-			p = cmdline + 1;
-			q = p + strlen(p) + 1;
-
-			/* open the stat file to figure out how long we have been running */
-			snprintf(path, sizeof(path), "/proc/%i/stat", pid);
-			if (!eat_file(path, &bufstat, &bufstat_len))
-				continue;
-
-			/* ripped from procps/proc/readproc.c */
-			if ((q = strchr(bufstat, ')')) == NULL)
-				continue;
-			/* grab the start time */
-			sscanf(q + 2,
-				"%*c "
-				"%*d %*d %*d %*d %*d "
-				"%*u %*u %*u %*u %*u "
-				"%*u %*u %*u %*u "
-				"%*d %*d "
-				"%*d "
-				"%*d "
-				"%llu ",
-				&start_time);
-			/* get uptime */
-			if (!eat_file("/proc/uptime", &bufstat, &bufstat_len))
-				continue;
-			sscanf(bufstat, "%lf", &uptime_secs);
-
-			/* figure out when this thing started and then show it */
-			start_date = time(0) - (uptime_secs - (start_time / hz));
-			printf(
-				" %s*%s %s%s%s\n"
-				"     started: %s%s%s\n"
-				"     elapsed: ", /*%s%llu%s seconds\n",*/
-				BOLD, NORM, BLUE, p, NORM,
-				GREEN, chop_ctime(start_date), NORM);
-			print_seconds_for_earthlings(uptime_secs - (start_time / hz));
-			puts(NORM);
-			p = root_readlink(pid);
-			if (p && strcmp(p, "/"))
-				printf("     chroot:  %s%s%s\n", GREEN, p, NORM);
-		}
-	}
-
-	closedir(proc);
-
-	if (start_time == 0 && verbose)
-		puts("No emerge processes located");
-}
-#elif defined(__FreeBSD__)
-# include <kvm.h>
-# include <sys/param.h>
-# include <sys/sysctl.h>
-# include <sys/user.h>
-void show_current_emerge(void)
-{
-	kvm_t *kd = NULL;
-	struct kinfo_proc *ip;
-	int i; int total_processes;
-	char *p, *q;
-	time_t start_date = 0;
-
-	if (! (kd = kvm_open("/dev/null", "/dev/null", "/dev/null",
-					O_RDONLY, "kvm_open")))
-	{
-		warnp("Could not open kvm: %s", kvm_geterr(kd));
-		return;
-	}
-
-	ip = kvm_getprocs(kd, KERN_PROC_PROC, 0, &total_processes);
-
-	for (i = 0; i < total_processes; i++) {
-		char **proc_argv = NULL;
-		char *buf = NULL;
-
-		if (strcmp(ip[i].ki_comm, "sandbox") != 0)
-			continue;
-
-		proc_argv = kvm_getargv(kd, &(ip[i]), 0);
-
-		if (!proc_argv || (buf = xstrdup(proc_argv[0])) == NULL ||
-		    buf[0] != '[' || (p = strchr(buf, ']')) == NULL) {
-			free(buf);
-			continue;
-		}
-
-		*p = '\0';
-		p = buf+1;
-		q = p + strlen(p) + 1;
-
-		printf(
-			" %s*%s %s%s%s\n"
-			"     started: %s%s%s\n"
-			"     elapsed: ", /*%s%llu%s seconds\n",*/
-			BOLD, NORM, BLUE, p, NORM,
-			GREEN, chop_ctime(ip[i].ki_start.tv_sec), NORM);
-		print_seconds_for_earthlings(time(0) - ip[i].ki_start.tv_sec);
-		puts(NORM);
-
-		free(buf);
-	}
-
-	if (start_date == 0 && verbose)
-		puts("No emerge processes located");
-}
-#elif defined(__MACH__)
-# include <sys/sysctl.h>
-void show_current_emerge(void)
-{
-	int mib[3];
-	size_t size = 0;
-	struct kinfo_proc *ip, *raip;
-	int ret, total_processes, i;
-	char *p, *q;
-	time_t start_date = 0;
-	char args[512];
-
-	mib[0] = CTL_KERN;
-	mib[1] = KERN_PROC;
-	mib[2] = KERN_PROC_ALL; /* could restrict to _UID (effective uid) */
-
-	/* probe once to get the current size; estimate only, but OS tries
-	 * to round up if it can predict a sudden growth, so optimise below
-	 * for the optimistic case */
-	ret = sysctl(mib, 3, NULL, &size, NULL, 0);
-	ip = xmalloc(sizeof(*ip) * size);
-	while (1) {
-		ret = sysctl(mib, 3, ip, &size, NULL, 0);
-		if (ret >= 0 && errno == ENOMEM) {
-			size += size / 10; /* may be a bit overdone... */
-			raip = realloc(ip, sizeof(struct kinfo_proc) * size);
-			if (raip == NULL) {
-				free(ip);
-				warnp("Could not extend allocated block to "
-						"%zd bytes for process information",
-						sizeof(struct kinfo_proc) * size);
-				return;
-			}
-			ip = raip;
-		} else if (ret < 0) {
-			free(ip);
-			warnp("Could not retrieve process information");
-			return;
-		} else {
-			break;
-		}
-	}
-
-	total_processes = size / sizeof(struct kinfo_proc);
-
-	/* initialise mib for argv retrieval calls */
-	mib[0] = CTL_KERN;
-	mib[1] = KERN_PROCARGS;
-
-	for (i = 0; i < total_processes; i++) {
-		char *buf = NULL;
-		size_t argssize = sizeof(args);
-
-		if (strcmp(ip[i].kp_proc.p_comm, "sandbox") != 0)
-			continue;
-
-		mib[2] = ip[i].kp_proc.p_pid;
-		if (sysctl(mib, 3, args, &argssize, NULL, 0) != 0) {
-			free(ip);
-			return;
-		}
-
-		/* this is magic to get back up in the stack where the arguments
-		 * start */
-		for (buf = args; buf < &args[argssize]; buf++)
-			if (*buf == '\0')
-				break;
-		if (buf == &args[argssize]) {
-			free(ip);
-			continue;
-		}
-		if ((buf = xstrdup(buf)) == NULL ||
-		    buf[0] != '[' || (p = strchr(buf, ']')) == NULL) {
-			free(buf);
-			continue;
-		}
-
-		*p = '\0';
-		p = buf+1;
-		q = p + strlen(p) + 1;
-
-		printf(
-			" %s*%s %s%s%s\n"
-			"     started: %s%s%s\n"
-			"     elapsed: ", /*%s%llu%s seconds\n",*/
-			BOLD, NORM, BLUE, p, NORM,
-			GREEN, chop_ctime(ip[i].kp_proc.p_starttime.tv_sec), NORM);
-		print_seconds_for_earthlings(time(0) - ip[i].kp_proc.p_starttime.tv_sec);
-		puts(NORM);
-
-		free(buf);
-	}
-
-	free(ip);
-
-	if (start_date == 0 && verbose)
-		puts("No emerge processes located");
-}
-#else
-void show_current_emerge(void)
-{
-	errf("not supported on your OS");
-}
-#endif
+struct qlop_mode {
+	char do_time:1;
+	char do_merge:1;
+	char do_unmerge:1;
+	char do_autoclean:1;
+	char do_sync:1;
+	char do_running:1;
+	char do_average:1;
+	char do_summary:1;
+	char do_human:1;
+	char do_endtime:1;
+};
 
 static bool
 parse_date(const char *sdate, time_t *t)
@@ -813,33 +168,688 @@ parse_date(const char *sdate, time_t *t)
 	return (*t == -1) ? false : true;
 }
 
+static char _date_buf[48];
+static char *fmt_date(struct qlop_mode *flags, time_t ts, time_t te)
+{
+	time_t t;
+
+	t = flags->do_endtime ? te : ts;
+	strftime(_date_buf, sizeof(_date_buf), "%Y-%m-%dT%H:%M:%S", localtime(&t));
+	return _date_buf;
+}
+
+static char _elapsed_buf[256];
+static char *fmt_elapsedtime(struct qlop_mode *flags, time_t e)
+{
+	if (flags->do_human) {
+		time_t dd;
+		time_t hh;
+		time_t mm;
+		time_t ss;
+		size_t bufpos = 0;
+
+		ss = e % 60;
+		e /= 60;
+		mm = e % 60;
+		e /= 60;
+		hh = e % 24;
+		e /= 24;
+		dd = e;
+
+		if (dd > 0)
+			bufpos += snprintf(_elapsed_buf + bufpos,
+					sizeof(_elapsed_buf) - bufpos,
+					"%s%zd%s day%s",
+					GREEN, (size_t)dd, NORM, dd == 1 ? "" : "s");
+		if (hh > 0)
+			bufpos += snprintf(_elapsed_buf + bufpos,
+					sizeof(_elapsed_buf) - bufpos,
+					"%s%s%zd%s hour%s",
+					bufpos == 0 ? "" : ", ",
+					GREEN, (size_t)hh, NORM, hh == 1 ? "" : "s");
+		if (mm > 0)
+			bufpos += snprintf(_elapsed_buf + bufpos,
+					sizeof(_elapsed_buf) - bufpos,
+					"%s%s%zd%s minute%s",
+					bufpos == 0 ? "" : ", ",
+					GREEN, (size_t)mm, NORM, mm == 1 ? "" : "s");
+		if (ss > 0 || (mm + hh + dd) == 0)
+			bufpos += snprintf(_elapsed_buf + bufpos,
+					sizeof(_elapsed_buf) - bufpos,
+					"%s%s%zd%s second%s",
+					bufpos == 0 ? "" : ", ",
+					GREEN, (size_t)ss, NORM, ss == 1 ? "" : "s");
+	} else {
+		snprintf(_elapsed_buf, sizeof(_elapsed_buf), "%s%zd%s seconds",
+				GREEN, (size_t)e, NORM);
+	}
+
+	return _elapsed_buf;
+}
+
+static char _atom_buf[BUFSIZ];
+static char *fmt_atom(struct qlop_mode *flags, depend_atom *atom)
+{
+	(void)flags;
+
+	if (verbose) {
+		size_t len = snprintf(_atom_buf, sizeof(_atom_buf), "%s/%s-%s",
+				atom->CATEGORY, atom->PN, atom->PV);
+		if (atom->PR_int > 0)
+			snprintf(_atom_buf + len, sizeof(_atom_buf) - len, "-r%d",
+				atom->PR_int);
+	} else {
+		snprintf(_atom_buf, sizeof(_atom_buf), "%s/%s",
+				atom->CATEGORY, atom->PN);
+	}
+
+	return _atom_buf;
+}
+
+/* The format of the sync log has changed over time.
+
+Old format:
+1106804103: Started emerge on: Jan 27, 2005 05:35:03
+1106804103:  *** emerge  sync
+1106804103:  === sync
+1106804103: >>> starting rsync with rsync://192.168.0.5/gentoo-portage
+1106804537: === Sync completed with rsync://192.168.0.5/gentoo-portage
+1106804538:  *** terminating.
+
+New format:
+1431764402: Started emerge on: May 16, 2015 04:20:01
+1431764402:  *** emerge --quiet --keep-going --verbose --nospinner --oneshot --quiet-build=n --sync
+1431764402:  === sync
+1431764402: >>> Syncing repository 'gentoo' into '/usr/portage'...
+1431764402: >>> Starting rsync with rsync://[2a01:90:200:10::1a]/gentoo-portage
+1431764460: === Sync completed for gentoo
+1431764493:  *** terminating.
+
+*** packages
+
+1547475773:  >>> emerge (53 of 74) app-shells/bash-5.0 to /gentoo/prefix64/
+1547475774:  === (53 of 74) Cleaning (app-shells/bash-5.0::/path/to/app-shells/bash/bash-5.0.ebuild)
+1547475774:  === (53 of 74) Compiling/Merging (app-shells/bash-5.0::/path/to/app-shells/bash/bash-5.0.ebuild)
+1547475913:  === (53 of 74) Merging (app-shells/bash-5.0::/path/to/app-shells/bash/bash-5.0.ebuild)
+1547475916:  >>> AUTOCLEAN: app-shells/bash:0
+1547475916:  === Unmerging... (app-shells/bash-4.4_p23)
+1547475918:  >>> unmerge success: app-shells/bash-4.4_p23
+1547475921:  === (53 of 74) Post-Build Cleaning (app-shells/bash-5.0::/path/to/app-shells/bash/bash-5.0.ebuild)
+1547475921:  ::: completed emerge (53 of 74) app-shells/bash-5.0 to /gentoo/prefix64/
+
+1550953093: Started emerge on: Feb 23, 2019 21:18:12
+1550953093:  *** emerge --ask --verbose --depclean pwgen
+1550953093:  >>> depclean
+1550953118: === Unmerging... (app-admin/pwgen-2.08)
+1550953125:  >>> unmerge success: app-admin/pwgen-2.08
+1550953125:  *** exiting successfully.
+1550953125:  *** terminating.
+*/
+static int do_emerge_log(
+		const char *log,
+		struct qlop_mode *flags,
+		array_t *atoms,
+		time_t tbegin,
+		time_t tend)
+{
+	FILE *fp;
+	char buf[BUFSIZ];
+	char *p;
+	char *q;
+	time_t tstart;
+	time_t sync_start = 0;
+	time_t sync_time = 0;
+	size_t sync_cnt = 0;
+	time_t elapsed;
+	depend_atom *atom;
+	depend_atom *atomw;
+	DECLARE_ARRAY(merge_matches);
+	DECLARE_ARRAY(merge_averages);
+	DECLARE_ARRAY(unmerge_matches);
+	DECLARE_ARRAY(unmerge_averages);
+	size_t i;
+	size_t parallel_emerge = 0;
+
+	struct pkg_match {
+		char id[BUFSIZ];
+		depend_atom *atom;
+		time_t tbegin;
+		time_t time;
+		size_t cnt;
+	};
+	struct pkg_match *pkg;
+	struct pkg_match *pkgw;
+
+	if ((fp = fopen(log, "r")) == NULL) {
+		warnp("Could not open logfile '%s'", log);
+		return 1;
+	}
+
+	if (array_cnt(atoms) == 0) {
+		/* assemble list of atoms */
+		while (fgets(buf, sizeof(buf), fp) != NULL) {
+			if ((p = strchr(buf, ':')) == NULL)
+				continue;
+			*p++ = '\0';
+
+			tstart = atol(buf);
+			if (tstart < tbegin || tstart > tend)
+				continue;
+
+			atom = NULL;
+			if (strncmp(p, "  >>> emerge ", 13) == 0 &&
+					(p = strchr(p + 13, ')')) != NULL)
+			{
+				p += 2;
+				q = strchr(p, ' ');
+				if (q != NULL) {
+					*q = '\0';
+					atom = atom_explode(p);
+				}
+			} else if (strncmp(p, "  === Unmerging... (", 20) == 0 ||
+					strncmp(p, " === Unmerging... (", 19) == 0)
+			{
+				if (p[1] == ' ')
+					p++;
+				p += 19;
+				q = strchr(p, ')');
+				if (q != NULL) {
+					*q = '\0';
+					atom = atom_explode(p);
+				}
+			}
+			if (atom != NULL) {
+				/* strip off version info, if we generate a list
+				 * ourselves, we will always print everything, so as
+				 * well can keep memory footprint a bit lower by only
+				 * having package matches */
+				atom->PV = NULL;
+				atom->PVR = NULL;
+				atom->PR_int = 0;
+
+				atomw = NULL;
+				array_for_each(atoms, i, atomw) {
+					if (atom_compare(atom, atomw) == EQUAL)
+						break;
+					atomw = NULL;
+				}
+				if (atomw == NULL) {
+					xarraypush_ptr(atoms, atom);
+				} else {
+					atom_implode(atom);
+				}
+			}
+		}
+
+		rewind(fp);
+	}
+
+	/* loop over lines searching for atoms */
+	while (fgets(buf, sizeof(buf), fp) != NULL) {
+		if ((p = strchr(buf, ':')) == NULL)
+			continue;
+		*p++ = '\0';
+
+		/* keeping track of parallel merges needs to be done before
+		 * applying dates, for a subset of the log might show emerge
+		 * finished without knowledge of another instance */
+		if (flags->do_running &&
+				(strncmp(p, "  *** emerge ", 13) == 0 ||
+				 strncmp(p, "  *** terminating.", 18) == 0 ||
+				 strncmp(p, "  *** exiting ", 14) == 0))
+		{
+			if (p[7] == 'm') {
+				parallel_emerge++;
+			} else if (parallel_emerge > 0) {
+				parallel_emerge--;
+				if (parallel_emerge == 0) {
+					/* we just finished the only emerge we found to be
+					 * running, so if there were "running" (unfinished)
+					 * merges, they must have been terminated */
+					sync_start = 0;
+					while ((i = array_cnt(merge_matches)) > 0) {
+						i--;
+						pkgw = xarrayget(merge_matches, i);
+						atom_implode(pkgw->atom);
+						xarraydelete(merge_matches, i);
+					}
+					while ((i = array_cnt(unmerge_matches)) > 0) {
+						i--;
+						pkgw = xarrayget(unmerge_matches, i);
+						atom_implode(pkgw->atom);
+						xarraydelete(unmerge_matches, i);
+					}
+				}
+			}
+		}
+
+		tstart = atol(buf);
+		if (tstart < tbegin || tstart > tend)
+			continue;
+
+		/* are we interested in this line? */
+		if (flags->do_sync && (
+					strncmp(p, " === Sync completed ", 20) == 0 ||
+					strcmp(p, "  === sync\n") == 0))
+		{
+			/* sync start or stop, we have nothing to detect parallel
+			 * syncs with, so don't bother and assume this doesn't
+			 * happen */
+			if (p[6] == 's') {
+				sync_start = tstart;
+			} else {
+				if (sync_start == 0)
+					continue;  /* sync without start, exclude */
+				elapsed = tstart - sync_start;
+
+				p += 20;
+				if (strncmp(p, "for ", 4) == 0) {
+					p += 4;
+				} else {  /* "with " */
+					p += 5;
+				}
+				if ((q = strchr(p, '\n')) != NULL)
+					*q = '\0';
+
+				if (flags->do_average || flags->do_running)
+				{
+					sync_cnt++;
+					sync_time += elapsed;
+					sync_start = 0;  /* reset */
+					continue;
+				}
+				if (flags->do_time) {
+					printf("%s *** %s%s%s: %s\n",
+							fmt_date(flags, sync_start, tstart),
+							GREEN, p, NORM, fmt_elapsedtime(flags, elapsed));
+				} else {
+					printf("%s *** %s%s%s\n",
+							fmt_date(flags, sync_start, tstart),
+							GREEN, p, NORM);
+				}
+				sync_start = 0;  /* reset */
+			}
+		} else if (flags->do_merge && (
+					strncmp(p, "  >>> emerge (", 14) == 0 ||
+					strncmp(p, "  ::: completed emerge (", 24) == 0))
+		{
+			/* merge start/stop (including potential unmerge of old pkg) */
+			if (p[3] == '>') {  /* >>> emerge */
+				char *id;
+
+				q = strchr(p + 14, ')');
+				if (q == NULL)
+					continue;
+
+				/* keep a copy of the relevant string in case we need to
+				 * match this */
+				id = p + 6;
+
+				q += 2;  /* ") " */
+				p = strchr(q, ' ');
+				if (p == NULL)
+					continue;
+
+				*p = '\0';
+				atom = atom_explode(q);
+				*p = ' ';
+				if (atom == NULL)
+					continue;
+
+				/* see if we need this atom */
+				atomw = NULL;
+				array_for_each(atoms, i, atomw) {
+					if (atom_compare(atom, atomw) == EQUAL)
+						break;
+					atomw = NULL;
+				}
+				if (atomw == NULL) {
+					atom_implode(atom);
+					continue;
+				}
+
+				pkg = xmalloc(sizeof(struct pkg_match));
+				snprintf(pkg->id, sizeof(pkg->id), "%s", id);
+				pkg->atom = atom;
+				pkg->tbegin = tstart;
+				pkg->time = (time_t)0;
+				pkg->cnt = 0;
+				xarraypush_ptr(merge_matches, pkg);
+			} else {  /* ::: completed */
+				array_for_each_rev(merge_matches, i, pkgw) {
+					if (strcmp(p + 16, pkgw->id) != 0)
+						continue;
+
+					/* found, do report */
+					elapsed = tstart - pkgw->tbegin;
+
+					if (flags->do_average || flags->do_running)
+					{
+						/* find in list of averages */
+						size_t n;
+
+						pkg = NULL;
+						array_for_each(merge_averages, n, pkg) {
+							if (atom_compare(pkg->atom, pkgw->atom) == EQUAL) {
+								pkg->cnt++;
+								pkg->time += elapsed;
+								/* store max time for do_running */
+								if (elapsed > pkg->tbegin)
+									pkg->tbegin = elapsed;
+								atom_implode(pkgw->atom);
+								xarraydelete(merge_matches, i);
+								break;
+							}
+							pkg = NULL;
+						}
+						if (pkg == NULL) {  /* push new entry */
+							if (!verbose || flags->do_running) {
+								/* strip off version info */
+								pkgw->atom->PV = NULL;
+								pkgw->atom->PVR = NULL;
+								pkgw->atom->PR_int = 0;
+							}
+							pkgw->id[0] = '\0';
+							pkgw->cnt = 1;
+							pkgw->time = elapsed;
+							pkgw->tbegin = elapsed;
+							xarraypush_ptr(merge_averages, pkgw);
+							xarraydelete_ptr(merge_matches, i);
+						}
+						break;
+					}
+					if (flags->do_time) {
+						printf("%s >>> %s%s%s: %s\n",
+								fmt_date(flags, pkgw->tbegin, tstart),
+								BLUE, fmt_atom(flags, pkgw->atom), NORM,
+								fmt_elapsedtime(flags, elapsed));
+					} else if (!flags->do_average) {
+						printf("%s >>> %s%s%s\n",
+								fmt_date(flags, pkgw->tbegin, tstart),
+								BLUE, fmt_atom(flags, pkgw->atom), NORM);
+					}
+					atom_implode(pkgw->atom);
+					xarraydelete(merge_matches, i);
+					break;
+				}
+			}
+		} else if (
+				(flags->do_unmerge &&
+				 strncmp(p, " === Unmerging... (", 19) == 0) ||
+				(flags->do_autoclean &&
+				 strncmp(p, "  === Unmerging... (", 20) == 0) ||
+				((flags->do_unmerge || flags->do_autoclean) &&
+				 strncmp(p, "  >>> unmerge success: ", 23) == 0))
+		{
+			/* unmerge action */
+			if (p[2] == '=') {
+				if (p[1] == ' ')
+					p++;
+				p += 19;
+
+				if ((q = strchr(p, ')')) == NULL)
+					continue;
+
+				*q = '\0';
+				atom = atom_explode(p);
+				if (atom == NULL)
+					continue;
+
+				/* see if we need this atom */
+				atomw = NULL;
+				array_for_each(atoms, i, atomw) {
+					if (atom_compare(atom, atomw) == EQUAL)
+						break;
+					atomw = NULL;
+				}
+				if (atomw == NULL) {
+					atom_implode(atom);
+					continue;
+				}
+
+				pkg = xmalloc(sizeof(struct pkg_match));
+				snprintf(pkg->id, sizeof(pkg->id), "%s\n", p);  /* \n !!! */
+				pkg->atom = atom;
+				pkg->tbegin = tstart;
+				pkg->time = (time_t)0;
+				pkg->cnt = 0;
+				xarraypush_ptr(unmerge_matches, pkg);
+			} else {
+				array_for_each_rev(unmerge_matches, i, pkgw) {
+					if (strcmp(p + 23, pkgw->id) != 0)
+						continue;
+
+					/* found, do report */
+					elapsed = tstart - pkgw->tbegin;
+
+					if (flags->do_average || flags->do_running)
+					{
+						/* find in list of averages */
+						size_t n;
+
+						pkg = NULL;
+						array_for_each(unmerge_averages, n, pkg) {
+							if (atom_compare(pkg->atom, pkgw->atom) == EQUAL) {
+								pkg->cnt++;
+								pkg->time += elapsed;
+								/* store max time for do_running */
+								if (elapsed > pkg->tbegin)
+									pkg->tbegin = elapsed;
+								atom_implode(pkgw->atom);
+								xarraydelete(unmerge_matches, i);
+								break;
+							}
+							pkg = NULL;
+						}
+						if (pkg == NULL) {  /* push new entry */
+							if (!verbose || flags->do_running) {
+								/* strip off version info */
+								pkgw->atom->PV = NULL;
+								pkgw->atom->PVR = NULL;
+								pkgw->atom->PR_int = 0;
+							}
+							pkgw->id[0] = '\0';
+							pkgw->cnt = 1;
+							pkgw->time = elapsed;
+							pkgw->tbegin = elapsed;
+							xarraypush_ptr(unmerge_averages, pkgw);
+							xarraydelete_ptr(unmerge_matches, i);
+						}
+						break;
+					}
+					if (flags->do_time) {
+						printf("%s <<< %s%s%s: %s\n",
+								fmt_date(flags, pkgw->tbegin, tstart),
+								BLUE, fmt_atom(flags, pkgw->atom), NORM,
+								fmt_elapsedtime(flags, elapsed));
+					} else if (!flags->do_average) {
+						printf("%s <<< %s%s%s\n",
+								fmt_date(flags, pkgw->tbegin, tstart),
+								BLUE, fmt_atom(flags, pkgw->atom), NORM);
+					}
+					atom_implode(pkgw->atom);
+					xarraydelete(unmerge_matches, i);
+					break;
+				}
+			}
+		}
+	}
+	fclose(fp);
+	if (flags->do_running) {
+		/* can't report endtime for non-finished operations */
+		flags->do_endtime = 0;
+		tstart = time(NULL);
+		sync_time /= sync_cnt;
+		if (sync_start > 0) {
+			if (elapsed >= sync_time)
+				sync_time = 0;
+			if (flags->do_time) {
+				elapsed = tstart - sync_start;
+				printf("%s *** %s%s%s: %s... ETA: %s\n",
+						fmt_date(flags, sync_start, 0),
+						YELLOW, "sync", NORM, fmt_elapsedtime(flags, elapsed),
+						sync_time == 0 ? "unknown" :
+							fmt_elapsedtime(flags, sync_time - elapsed));
+			} else {
+				printf("%s *** %s%s%s... ETA: %s\n",
+						fmt_date(flags, sync_start, 0),
+						YELLOW, "sync", NORM,
+						sync_time == 0 ? "unknown" :
+							fmt_elapsedtime(flags, sync_time - elapsed));
+			}
+		}
+		array_for_each(merge_matches, i, pkgw) {
+			size_t j;
+			time_t maxtime = 0;
+
+			elapsed = tstart - pkgw->tbegin;
+			pkg = NULL;
+			array_for_each(merge_averages, j, pkg) {
+				if (atom_compare(pkg->atom, pkgw->atom) == EQUAL) {
+					maxtime = pkg->time / pkg->cnt;
+					if (elapsed >= maxtime)
+						maxtime = elapsed >= pkg->tbegin ? 0 : pkg->tbegin;
+					break;
+				}
+				pkg = NULL;
+			}
+
+			if (flags->do_time) {
+				printf("%s >>> %s%s%s: %s... ETA: %s\n",
+						fmt_date(flags, pkgw->tbegin, 0),
+						YELLOW, fmt_atom(flags, pkgw->atom), NORM,
+						fmt_elapsedtime(flags, elapsed),
+						maxtime == 0 ? "unknown" :
+							fmt_elapsedtime(flags, maxtime - elapsed));
+			} else {
+				printf("%s >>> %s%s%s... ETA: %s\n",
+						fmt_date(flags, pkgw->tbegin, 0),
+						YELLOW, fmt_atom(flags, pkgw->atom), NORM,
+						maxtime == 0 ? "unknown" :
+							fmt_elapsedtime(flags, maxtime - elapsed));
+			}
+		}
+		array_for_each(unmerge_matches, i, pkgw) {
+			size_t j;
+			time_t maxtime = 0;
+
+			elapsed = tstart - pkgw->tbegin;
+			pkg = NULL;
+			array_for_each(unmerge_averages, j, pkg) {
+				if (atom_compare(pkg->atom, pkgw->atom) == EQUAL) {
+					maxtime = pkg->time / pkg->cnt;
+					if (elapsed >= maxtime)
+						maxtime = elapsed >= pkg->tbegin ? 0 : pkg->tbegin;
+					break;
+				}
+				pkg = NULL;
+			}
+
+			if (flags->do_time) {
+				printf("%s <<< %s%s%s: %s... ETA: %s\n",
+						fmt_date(flags, pkgw->tbegin, 0),
+						YELLOW, fmt_atom(flags, pkgw->atom), NORM,
+						fmt_elapsedtime(flags, elapsed),
+						maxtime == 0 ? "unknown" :
+							fmt_elapsedtime(flags, maxtime - elapsed));
+			} else {
+				printf("%s <<< %s%s%s... ETA: %s\n",
+						fmt_date(flags, pkgw->tbegin, 0),
+						YELLOW, fmt_atom(flags, pkgw->atom), NORM,
+						maxtime == 0 ? "unknown" :
+							fmt_elapsedtime(flags, maxtime - elapsed));
+			}
+		}
+	} else if (flags->do_average) {
+		size_t total_merges = 0;
+		size_t total_unmerges = 0;
+		time_t total_time = (time_t)0;
+
+		array_for_each(merge_averages, i, pkg) {
+			printf("%s%s%s: %s average for %s%zd%s merge%s\n",
+					BLUE, fmt_atom(flags, pkg->atom), NORM,
+					fmt_elapsedtime(flags, pkg->time / pkg->cnt),
+					GREEN, pkg->cnt, NORM, pkg->cnt == 1 ? "" : "s");
+			total_merges += pkg->cnt;
+			total_time += pkg->time;
+		}
+		array_for_each(unmerge_averages, i, pkg) {
+			printf("%s%s%s: %s average for %s%zd%s unmerge%s\n",
+					BLUE, fmt_atom(flags, pkg->atom), NORM,
+					fmt_elapsedtime(flags, pkg->time / pkg->cnt),
+					GREEN, pkg->cnt, NORM, pkg->cnt == 1 ? "" : "s");
+			total_unmerges += pkg->cnt;
+			total_time += pkg->time;
+		}
+		if (sync_cnt > 0) {
+			printf("%ssync%s: %s average for %s%zd%s sync%s\n",
+					BLUE, NORM, fmt_elapsedtime(flags, sync_time / sync_cnt),
+					GREEN, sync_cnt, NORM, sync_cnt == 1 ? "" : "s");
+			total_time += sync_time;
+		}
+		if (flags->do_summary) {
+			/* 123 seconds for 5 merges, 3 unmerges, 1 sync */
+			printf("%stotal%s: %s for ",
+					BLUE, NORM, fmt_elapsedtime(flags, total_time));
+			if (total_merges > 0)
+				printf("%s%zd%s merge%s",
+					GREEN, total_merges, NORM, total_merges == 1 ? "" : "s");
+			if (total_unmerges > 0)
+				printf("%s%s%zd%s unmerge%s",
+						total_merges == 0 ? "" : ", ",
+						GREEN, total_unmerges, NORM,
+						total_unmerges == 1 ? "" : "s");
+			if (sync_cnt > 0)
+				printf("%s%s%zd%s sync%s",
+						total_merges + total_unmerges == 0 ? "" : ", ",
+						GREEN, sync_cnt, NORM, sync_cnt == 1 ? "" : "s");
+			printf("\n");
+		}
+	}
+	return 0;
+}
+
 int qlop_main(int argc, char **argv)
 {
 	size_t i;
 	int ret;
-	int average = 1;
-	time_t start_time, end_time;
-	char do_time, do_list, do_unlist, do_sync, do_current, do_human_readable = 0;
+	time_t start_time;
+	time_t end_time;
+	struct qlop_mode m;
 	char *logfile = NULL;
-	int flags;
+	char *atomfile = NULL;
+	char *p;
+	char *q;
 	depend_atom *atom;
 	DECLARE_ARRAY(atoms);
 
 	start_time = 0;
 	end_time = LONG_MAX;
-	do_time = do_list = do_unlist = do_sync = do_current = 0;
+	m.do_time = 0;
+	m.do_merge = 0;
+	m.do_unmerge = 0;
+	m.do_autoclean = 0;
+	m.do_sync = 0;
+	m.do_running = 0;
+	m.do_average = 0;
+	m.do_summary = 0;
+	m.do_human = 0;
+	m.do_endtime = 0;
 
 	while ((ret = GETOPT_LONG(QLOP, qlop, "")) != -1) {
 		switch (ret) {
 			COMMON_GETOPTS_CASES(qlop)
 
-			case 't': do_time = 1; break;
-			case 'l': do_list = 1; break;
-			case 'u': do_unlist = 1; break;
-			case 's': do_sync = 1; break;
-			case 'c': do_current = 1; break;
-			case 'g': do_time = 1; average = 0; break;
-			case 'H': do_human_readable = 1; break;
+			case 't': m.do_time = 1;      break;
+			case 'm': m.do_merge = 1;     break;
+			case 'u': m.do_unmerge = 1;   break;
+			case 'U': m.do_autoclean = 1; break;
+			case 's': m.do_sync = 1;      break;
+			case 'r': m.do_running = 1;   break;
+			case 'a': m.do_average = 1;   break;
+			case 'c': m.do_summary = 1;   break;
+			case 'H': m.do_human = 1;     break;
+			case 'e': m.do_endtime = 1;   break;
 			case 'd':
 				if (start_time == 0) {
 					if (!parse_date(optarg, &start_time))
@@ -851,13 +861,19 @@ int qlop_main(int argc, char **argv)
 					err("too many -d options");
 				break;
 			case 'f':
-				if (logfile) err("Only use -f once");
+				if (logfile != NULL)
+					err("Only use -f once");
 				logfile = xstrdup(optarg);
 				break;
+			case 'w':
+				if (atomfile != NULL)
+					err("Only use -w once");
+				if (!eat_file(optarg, &atomfile, &i))
+					err("failed to open file %s", optarg);
+				break;
 		}
 	}
-	if (!do_list && !do_unlist && !do_time && !do_sync && !do_current)
-		qlop_usage(EXIT_FAILURE);
+
 	if (logfile == NULL)
 		xasprintf(&logfile, "%s/%s", portlogdir, QLOP_DEFAULT_LOGFILE);
 
@@ -870,26 +886,75 @@ int qlop_main(int argc, char **argv)
 		else
 			xarraypush_ptr(atoms, atom);
 	}
+	for (p = atomfile; p != NULL && *p != '\0'; p = q) {
+		while (isspace((int)(*p)))
+			p++;
+		q = strchr(p, '\n');
+		if (q != NULL) {
+			*q = '\0';
+			q++;
+		}
+		atom = atom_explode(p);
+		if (!atom)
+			warn("invalid atom: %s", p);
+		else
+			xarraypush_ptr(atoms, atom);
+	}
+	if (atomfile)
+		free(atomfile);
+
+	/* default operation: -must */
+	if (
+			m.do_time == 0 &&
+			m.do_merge == 0 &&
+			m.do_unmerge == 0 &&
+			m.do_autoclean == 0 &&
+			m.do_sync == 0 &&
+			m.do_running == 0 &&
+			m.do_average == 0 &&
+			m.do_summary == 0 &&
+			m.do_human == 0
+		)
+	{
+		m.do_merge = 1;
+		m.do_unmerge = 1;
+		m.do_sync = 1;
+		m.do_time = 1;
+	}
+
+	/* handle deps */
+	if (m.do_summary)
+		m.do_average = 1;
+
+	/* handle -a / -t conflict */
+	if (m.do_average && m.do_time) {
+		warn("-a (or -c) and -t cannot be used together, dropping -t");
+		m.do_time = 0;
+	}
 
-	flags = 0;
-	if (do_list)
-		flags |= QLOP_LIST;
-	if (do_unlist)
-		flags |= QLOP_UNLIST;
-	if (flags)
-		show_emerge_history(flags, atoms, logfile, start_time, end_time);
-
-	if (do_current)
-		show_current_emerge();
-	if (do_sync)
-		show_sync_history(logfile, start_time, end_time);
-
-	if (do_time) {
-		for (i = 0; i < (size_t)argc; ++i)
-			show_merge_times(argv[i], logfile, average, do_human_readable,
-				start_time, end_time);
+	/* handle -a / -r conflict */
+	if (m.do_average && m.do_running) {
+		warn("-a (or -c) and -r cannot be used together, dropping -a");
+		m.do_average = 0;
 	}
 
+	if (m.do_sync && array_cnt(atoms) > 0) {
+		warn("-s cannot be used when specifying atoms, dropping -s");
+		m.do_sync = 0;
+	}
+
+	/* set default for -t, -a or -r */
+	if ((m.do_average || m.do_time || m.do_running) &&
+			!(m.do_merge || m.do_unmerge || m.do_sync))
+	{
+		m.do_merge = 1;
+		m.do_unmerge = 1;
+		if (array_cnt(atoms) == 0)
+			m.do_sync = 1;
+	}
+
+	do_emerge_log(logfile, &m, atoms, start_time, end_time);
+
 	array_for_each(atoms, i, atom)
 		atom_implode(atom);
 	xarrayfree_int(atoms);

diff --git a/tests/qlop/dotest b/tests/qlop/dotest
index ac30924..51cdb8a 100755
--- a/tests/qlop/dotest
+++ b/tests/qlop/dotest
@@ -25,26 +25,26 @@ export LC_TIME="C"
 # simple sync check
 test 01 0 "qlop -s -f ${as}/sync.log"
 
-# check all installed pkgs
-test 02 0 "qlop -l -f ${as}/sync.log"
+# check all merged pkgs
+test 02 0 "qlop -mv -f ${as}/sync.log"
 
-# check all uninstalled pkgs
-test 03 0 "qlop -u -f ${as}/sync.log"
+# check all unmerged pkgs
+test 03 0 "qlop -uv -f ${as}/sync.log"
 
 # verify atom parsing works (and not partial strings)
-test 04 0 "qlop -l gcc -f ${as}/sync.log"
+test 04 0 "qlop -mv gcc -f ${as}/sync.log"
 
 # verify atom version parsing works
-test 05 0 "qlop -l '<gcc-5' -f ${as}/sync.log"
+test 05 0 "qlop -mv '<gcc-5' -f ${as}/sync.log"
 
 # check date time parser, note on date parsing,
 # https://bugs.gentoo.org/638032#c6 so the format %d%Y%m isn't compliant
-test 06 0 "qlop -l -f ${as}/sync.log -d 2005-01-01"
-test 07 0 "qlop -l -f ${as}/sync.log -d '%d %Y %m|01 2005 01'"
-test 08 0 "qlop -l -f ${as}/sync.log -d 1104898893"
+test 06 0 "qlop -mv -f ${as}/sync.log -d 2005-01-01"
+test 07 0 "qlop -mv -f ${as}/sync.log -d '%d %Y %m|01 2005 01'"
+test 08 0 "qlop -mv -f ${as}/sync.log -d 1104898893"
 
 # deal with aborted merges
-test 09 0 "qlop -Htgv automake -f ${as}/aborts.log"
+test 09 0 "qlop -Hacv automake -f ${as}/aborts.log"
 
 cleantmpdir
 

diff --git a/tests/qlop/list01.good b/tests/qlop/list01.good
index fc8683c..2689952 100644
--- a/tests/qlop/list01.good
+++ b/tests/qlop/list01.good
@@ -1,2 +1,2 @@
-Thu Jan 27 05:42:17 2005 >>> rsync://192.168.0.5/gentoo-portage
-Sat May 16 08:21:00 2015 >>> gentoo
+2005-01-27T05:35:03 *** rsync://192.168.0.5/gentoo-portage
+2015-05-16T08:20:02 *** gentoo

diff --git a/tests/qlop/list02.good b/tests/qlop/list02.good
index a00a3f6..706bdac 100644
--- a/tests/qlop/list02.good
+++ b/tests/qlop/list02.good
@@ -1,3 +1,3 @@
-Thu Oct 28 06:49:14 2004 >>> dev-util/ccache-2.3
-Thu Jan 27 05:56:39 2005 >>> sys-devel/gcc-config-1.3.9
-Thu Jan 27 06:17:10 2005 >>> sys-devel/gcc-3.4.3-r1
+2004-10-28T06:49:13 >>> dev-util/ccache-2.3
+2005-01-27T05:56:28 >>> sys-devel/gcc-config-1.3.9
+2005-01-27T05:56:39 >>> sys-devel/gcc-3.4.3-r1

diff --git a/tests/qlop/list03.good b/tests/qlop/list03.good
index add774a..cccc8bb 100644
--- a/tests/qlop/list03.good
+++ b/tests/qlop/list03.good
@@ -1,4 +1,4 @@
-Thu Jan 27 05:56:39 2005 <<< sys-devel/gcc-config-1.3.6-r3
-Thu Jan 27 05:58:04 2005 <<< sys-apps/pam-login-3.14
-Thu Jan 27 05:58:06 2005 <<< sys-libs/pam-0.77-r1
-Thu Jan 27 05:58:16 2005 <<< sys-fs/devfsd-1.3.25-r8
+2005-01-27T05:56:38 <<< sys-devel/gcc-config-1.3.6-r3
+2005-01-27T05:58:02 <<< sys-apps/pam-login-3.14
+2005-01-27T05:58:04 <<< sys-libs/pam-0.77-r1
+2005-01-27T05:58:15 <<< sys-fs/devfsd-1.3.25-r8

diff --git a/tests/qlop/list04.good b/tests/qlop/list04.good
index c3de519..aa39ed2 100644
--- a/tests/qlop/list04.good
+++ b/tests/qlop/list04.good
@@ -1 +1 @@
-Thu Jan 27 06:17:10 2005 >>> sys-devel/gcc-3.4.3-r1
+2005-01-27T05:56:39 >>> sys-devel/gcc-3.4.3-r1

diff --git a/tests/qlop/list05.good b/tests/qlop/list05.good
index c3de519..aa39ed2 100644
--- a/tests/qlop/list05.good
+++ b/tests/qlop/list05.good
@@ -1 +1 @@
-Thu Jan 27 06:17:10 2005 >>> sys-devel/gcc-3.4.3-r1
+2005-01-27T05:56:39 >>> sys-devel/gcc-3.4.3-r1

diff --git a/tests/qlop/list06.good b/tests/qlop/list06.good
index e9fccc3..769aa7b 100644
--- a/tests/qlop/list06.good
+++ b/tests/qlop/list06.good
@@ -1,2 +1,2 @@
-Thu Jan 27 05:56:39 2005 >>> sys-devel/gcc-config-1.3.9
-Thu Jan 27 06:17:10 2005 >>> sys-devel/gcc-3.4.3-r1
+2005-01-27T05:56:28 >>> sys-devel/gcc-config-1.3.9
+2005-01-27T05:56:39 >>> sys-devel/gcc-3.4.3-r1

diff --git a/tests/qlop/list07.good b/tests/qlop/list07.good
index e9fccc3..769aa7b 100644
--- a/tests/qlop/list07.good
+++ b/tests/qlop/list07.good
@@ -1,2 +1,2 @@
-Thu Jan 27 05:56:39 2005 >>> sys-devel/gcc-config-1.3.9
-Thu Jan 27 06:17:10 2005 >>> sys-devel/gcc-3.4.3-r1
+2005-01-27T05:56:28 >>> sys-devel/gcc-config-1.3.9
+2005-01-27T05:56:39 >>> sys-devel/gcc-3.4.3-r1

diff --git a/tests/qlop/list08.good b/tests/qlop/list08.good
index e9fccc3..769aa7b 100644
--- a/tests/qlop/list08.good
+++ b/tests/qlop/list08.good
@@ -1,2 +1,2 @@
-Thu Jan 27 05:56:39 2005 >>> sys-devel/gcc-config-1.3.9
-Thu Jan 27 06:17:10 2005 >>> sys-devel/gcc-3.4.3-r1
+2005-01-27T05:56:28 >>> sys-devel/gcc-config-1.3.9
+2005-01-27T05:56:39 >>> sys-devel/gcc-3.4.3-r1

diff --git a/tests/qlop/list09.good b/tests/qlop/list09.good
index 333d7ad..269ec03 100644
--- a/tests/qlop/list09.good
+++ b/tests/qlop/list09.good
@@ -1,3 +1,3 @@
-automake-1.11.6: Mon Apr  1 12:34:09 2013: 53 minutes, 52 seconds
-automake-1.9.6-r3: Fri May 17 12:58:39 2013: 24 minutes, 7 seconds
-automake: 2 times
+sys-devel/automake-1.11.6: 53 minutes, 52 seconds average for 1 merge
+sys-devel/automake-1.9.6-r3: 24 minutes, 7 seconds average for 1 merge
+total: 1 hour, 17 minutes, 59 seconds for 2 merges


^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2019-02-27 20:53 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2019-02-27 20:53 [gentoo-commits] proj/portage-utils:master commit in: /, tests/qlop/, man/include/, man/ Fabian Groffen

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox