From 82fe1f044ac6321bc27d482cc5ec698c664b6753 Mon Sep 17 00:00:00 2001
From: Shlomi Fish <shlomif@shlomifish.org>
Date: Mon, 22 Apr 2019 23:09:25 +0300
Subject: [PATCH] forward port the profiling support.

See https://github.com/eddyp/make-profiler/branches .

+Compile fixes by Shlomi Fish:

<<<
I hereby disclaim any implicit or explicit ownership of my changes in this
changeset, and put them under a multiple licence consisting of your choice of
one of more of:

- The CC0 / Public Domain - https://creativecommons.org/choose/zero/ .

- The MIT / Expat license - https://en.wikipedia.org/wiki/MIT_License

- The default licence of your project

- The https://en.wikipedia.org/wiki/GNU_Lesser_General_Public_License - version
2.1 or higher

- The https://en.wikipedia.org/wiki/GNU_General_Public_License - version 2 or
higher

- Any licence in the 2018-Aug-27 popular licenses list of
https://opensource.org/licenses

- The https://en.wikipedia.org/wiki/Apache_License version 2.0 or later

- The https://en.wikipedia.org/wiki/Artistic_License version 2.0 or later

- The https://en.wikipedia.org/wiki/ISC_license

- The https://opensource.org/licenses/BSD-2-Clause

Crediting me will be nice, but not mandatory, and you can change the licence
of the project without needing my permission.
>>>
---
 README.profiling-make.md |  16 +++
 doc/make.1               |  55 +++++++++
 src/file.c               | 247 ++++++++++++++++++++++++++++++++++++++-
 src/filedef.h            |  23 ++++
 src/main.c               | 218 +++++++++++++++++++++++++++++-----
 src/remake.c             |   2 +
 6 files changed, 528 insertions(+), 33 deletions(-)
 create mode 100644 README.profiling-make.md

diff --git a/README.profiling-make.md b/README.profiling-make.md
new file mode 100644
index 0000000..8dd28e4
--- /dev/null
+++ b/README.profiling-make.md
@@ -0,0 +1,16 @@
+make-profiler
+=============
+
+GNU Make with profiling feature
+
+Adds options two options useful for profiling: -g/--graph and -P/--profile.
+
+When passing -g/--graph to GNU Make to generate a Graphviz file instead of building.
+When -P/--profile/--profile-format is given, GNU Make will print profiling information for each of the targets to allow profiling.
+
+This work is based on:
+ - Luke Shumaker's "Create graph output" patch from http://savannah.gnu.org/patch/?7447
+ - OpenShift guest <zlqhem@daum.net>'s "Update time" patches and fixes
+
+TODO:
+ - make graph output customizable via a <makefile>.graphcfg file that allows disabling the printing of specific targets/target types in the graph
diff --git a/doc/make.1 b/doc/make.1
index 4271ec5..2fc6fe2 100644
--- a/doc/make.1
+++ b/doc/make.1
@@ -258,6 +258,61 @@ switch (see below).
 To print the data base without trying to remake any files, use
 .IR "make \-p \-f/dev/null" .
 .TP 0.5i
+\fB\-P\fR[\fIFMT\fR], \fB\-\-profile\fR[=\fIFMT\fR], \fB\-\-profile\-format\fR[=\fIFMT\fR]
+Print to \fIstderr\fR profiling information for each run target, one line per target, according to format string
+.IR "FMT" .
+.SP 1
+The
+.I FMT
+string can contain plain text and the following formatting sequences:
+.RS 8
+.PP
+\fB%N\fR target name
+.RE
+.RS 8
+\fB%L\fR recursion level for the make instance processing the target
+.RE
+.RS 8
+\fB%P\fR
+pid of the make instance processing the target
+.RE
+.RS 8
+\fB%S\fR
+start target timestamp (in miliseconds since 1 ian 1970)
+.RE
+.RS 8
+\fB%E\fR
+end target timestamp (in miliseconds since 1 ian 1970)
+.RE
+.RS 8
+\fB%D\fR
+difference between the target start and end timestamps (in miliseconds)
+.RE
+.RS 8
+\fB%%\fR
+literal %
+.RE
+.PP
+.RS 8
+If the option is repeated, all the
+.I FMT
+parameters are concatenated internally into a single formatting string.
+.PP
+Any unrecognized
+.I %
+sequence will generate an error.
+If the
+.I FMT
+string ends with a single
+.I %
+character, then that character will be ignored.
+.PP
+The default
+.I FMT
+string is
+.BR "[PROF:%N:lvl=%L:pid=%P] %S;%E;%D" .
+.RE
+.TP 0.5i
 \fB\-q\fR, \fB\-\-question\fR
 ``Question mode''.
 Do not run any commands, or print anything; just return an exit status
diff --git a/src/file.c b/src/file.c
index 925ce8d..5ef0948 100644
--- a/src/file.c
+++ b/src/file.c
@@ -208,7 +208,7 @@ enter_file (const char *name)
 
   return new;
 }
-
+
 /* Rehash FILE to NAME.  This is not as simple as resetting
    the 'hname' member, since it must be put in a new hash bucket,
    and possibly merged with an existing file called NAME.  */
@@ -355,7 +355,7 @@ rename_file (struct file *from_file, const char *to_hname)
       from_file = from_file->prev;
     }
 }
-
+
 /* Remove all nonprecious intermediate files.
    If SIG is nonzero, this was caused by a fatal signal,
    meaning that a different message will be printed, and
@@ -803,7 +803,7 @@ set_command_state (struct file *file, enum cmd_state state)
     if (state > d->file->command_state)
       d->file->command_state = state;
 }
-
+
 /* Convert an external file timestamp to internal form.  */
 
 FILE_TIMESTAMP
@@ -827,7 +827,7 @@ file_timestamp_cons (const char *fname, time_t stamp, long int ns)
 
   return ts;
 }
-
+
 /* Return the current time as a file timestamp, setting *RESOLUTION to
    its resolution.  */
 FILE_TIMESTAMP
@@ -908,7 +908,7 @@ file_timestamp_sprintf (char *p, FILE_TIMESTAMP ts)
 
   *p = '\0';
 }
-
+
 /* Print the data base of files.  */
 
 void
@@ -1058,7 +1058,242 @@ print_file_data_base (void)
   fputs (_("\n# files hash-table stats:\n# "), stdout);
   hash_print_stats (&files, stdout);
 }
-
+
+/* Dump the dependency graph to a Graphviz file (on stdout)  */
+
+void
+print_graph_prereqs (const char *filename, const struct dep *deps)
+{
+  const struct dep *ood = 0;
+
+  /* Print all normal dependencies; note any order-only deps.  */
+  for (; deps != 0; deps = deps->next)
+    if (! deps->ignore_mtime)
+      printf ("  \"%s\" -> \"%s\" [%s %s] ;\n", filename, dep_name (deps),
+              deps->changed ? "color=red": "",
+              (deps->flags & RM_DONTCARE) ? "arrowhead=empty" : "arrowhead=normal");
+
+  /* Print order-only deps, if we have any.  */
+  if (ood)
+    {
+      for (ood = ood->next; ood != 0; ood = ood->next)
+        if (ood->ignore_mtime)
+          printf ("    %s -> %s [style=dashed];\n", filename, dep_name (ood));
+          /* XXX: we need to distinguish these some how.
+           * Is dotting them the right way? */
+    }
+}
+
+static void
+print_graph_file (const void *item)
+{
+  const struct file *f = item;
+
+  int built_in_special_target=(
+       (0==strcmp(f->name,".PHONY"))
+    || (0==strcmp(f->name,".SUFFIXES"))
+    || (0==strcmp(f->name,".DEFAULT"))
+    || (0==strcmp(f->name,".PRECIOUS"))
+    || (0==strcmp(f->name,".INTERMEDIATE"))
+    || (0==strcmp(f->name,".SECONDARY"))
+    || (0==strcmp(f->name,".SECONDEXPANSION"))
+    || (0==strcmp(f->name,".DELETE_ON_ERROR"))
+    || (0==strcmp(f->name,".IGNORE"))
+    || (0==strcmp(f->name,".LOW_RESOLUTION_TIME"))
+    || (0==strcmp(f->name,".SILENT"))
+    || (0==strcmp(f->name,".EXPORT_ALL_VARIABLES"))
+    || (0==strcmp(f->name,".NOTPARALLEL"))
+    || (0==strcmp(f->name,".ONESHELL"))
+    || (0==strcmp(f->name,".POSIX"))
+    );
+  if ((f->is_target) && (!built_in_special_target))
+    {
+      printf ("  \"%s\" [", f->name);
+      /* XXX some of these should be attached to the nodes in some way;
+       * though I'm not sure what style changes should be made for which ones.
+       *  ~ LukeShu*/
+      if (f->double_colon)   printf (" shape=tripleoctagon");
+      if (f->precious)       printf (" fontcolor=red");
+      if (f->phony)          printf (" style=dashed color=blue");
+      else                   printf (" style=filled fillcolor=green");
+      if (f->cmd_target)     printf (" shape=diamond root");
+      if (f->dontcare)       printf (" shape=parallelogram");
+      if (f->tried_implicit) printf (" shape=underline");
+      if (f->intermediate)   printf (" style=dotted color=yellow");
+      #if 0
+      if (f->stem != 0)      printf (" shape=box3d label=\"\\N (%s)\"", f->stem); /* Implicit/static pattern stem: f->stem */
+      if (f->also_make != 0)
+        {
+          const struct dep *d;
+          fputs (_("#  Also makes:"), stdout);
+          for (d = f->also_make; d != 0; d = d->next)
+          printf (" %s\n", dep_name (d));
+        }
+           if (f->last_mtime == UNKNOWN_MTIME)     puts (_("// Modification time never checked."));
+      else if (f->last_mtime == NONEXISTENT_MTIME) puts (_("// File does not exist."));
+      else if (f->last_mtime == OLD_MTIME)         puts (_("// File is very old."));
+      else
+        {
+          char buf[FILE_TIMESTAMP_PRINT_LEN_BOUND + 1];
+          file_timestamp_sprintf (buf, f->last_mtime);
+          printf (_("// Last modified %s\n"), buf);
+        }
+      if (f->updated) puts (_("// File has been updated."));
+      else            puts (_("// File has not been updated."));
+      switch (f->command_state)
+        {
+        case cs_running:      puts (_("// Update: Running (THIS IS A BUG).")); break;
+        case cs_deps_running: puts (_("// Update: Dependencies running (THIS IS A BUG).")); break;
+        case cs_not_started:
+        case cs_finished:
+          switch (f->update_status)
+	    {
+	    case -1: break;
+	    case 0:  puts (_("// Update: Successfully")); break;
+	    case 1:  assert (question_flag);
+	             puts (_("// Update: Needs to be (-q is set)")); break;
+	    case 2:  puts (_("// Update: Failed")); break;
+	    default: puts (_("// Update: Invalid `update_status' value"));
+	            fflush (stdout);
+	            fflush (stderr);
+	            abort ();
+	    }
+          break;
+        default: puts (_("// Update: Invalid `command_state' value"));
+                 fflush (stdout);
+                 fflush (stderr);
+                 abort ();
+        }
+      if (f->variables != 0) print_file_variables (f);
+      if (f->cmds != 0) print_commands (f->cmds);
+      #endif
+      printf("];\n");
+      print_graph_prereqs (f->name, f->deps);
+    }
+
+  if (f->prev)
+    print_graph_file ((const void *) f->prev);
+}
+
+void
+print_graph (void)
+{
+  printf ("%sgraph make%i {\n",
+          (makelevel==0)?"di":"sub",
+          getpid());
+  hash_map (&files, print_graph_file);
+  puts ("}");
+}
+
+static double
+invoke_time(const struct file *f)
+{
+  double invoked_time_in_mill = (f->t_invoked.tv_sec) * 1000 +
+            (f->t_invoked.tv_usec) / 1000;
+  return invoked_time_in_mill;
+}
+
+static double
+finish_time(const struct file *f)
+{
+  double finished_time_in_mill = (f->t_finished.tv_sec) * 1000 +
+            (f->t_finished.tv_usec) / 1000;
+  return finished_time_in_mill;
+}
+
+void
+prof_print_str (__attribute__ ((unused)) const struct file *f, char *fmt)
+{
+  fprintf (stderr, "%s", fmt);
+}
+
+void
+prof_print_name (const struct file *f, __attribute__((unused)) char *fmt)
+{
+  fprintf (stderr, "%s", f->name);
+}
+
+void
+prof_print_level (__attribute__((unused)) const struct file *f,
+    __attribute__((unused)) char *fmt)
+{
+  fprintf (stderr, "%u", makelevel);
+}
+
+void
+prof_print_pid (__attribute__((unused)) const struct file *f,
+    __attribute__((unused)) char *fmt)
+{
+  fprintf (stderr, "%d", getpid());
+}
+
+void
+prof_print_invokets (const struct file *f, __attribute__((unused)) char *fmt)
+{
+  fprintf (stderr, "%.0f", invoke_time(f));
+}
+
+void
+prof_print_finishts (const struct file *f, __attribute__((unused)) char *fmt)
+{
+  fprintf (stderr, "%.0f", finish_time(f));
+}
+
+void
+prof_print_diff (const struct file *f, __attribute__((unused)) char *fmt)
+{
+  double itime = invoke_time(f);
+  double ftime = finish_time(f);
+  fprintf (stderr, "%.0f", ftime-itime);
+}
+
+static void
+print_target_update_time (const void *item)
+{
+  const struct file *f = item;
+
+  int built_in_special_target=(
+       (0==strcmp(f->name,".PHONY"))
+    || (0==strcmp(f->name,".SUFFIXES"))
+    || (0==strcmp(f->name,".DEFAULT"))
+    || (0==strcmp(f->name,".PRECIOUS"))
+    || (0==strcmp(f->name,".INTERMEDIATE"))
+    || (0==strcmp(f->name,".SECONDARY"))
+    || (0==strcmp(f->name,".SECONDEXPANSION"))
+    || (0==strcmp(f->name,".DELETE_ON_ERROR"))
+    || (0==strcmp(f->name,".IGNORE"))
+    || (0==strcmp(f->name,".LOW_RESOLUTION_TIME"))
+    || (0==strcmp(f->name,".SILENT"))
+    || (0==strcmp(f->name,".EXPORT_ALL_VARIABLES"))
+    || (0==strcmp(f->name,".NOTPARALLEL"))
+    || (0==strcmp(f->name,".ONESHELL"))
+    || (0==strcmp(f->name,".POSIX"))
+    );
+  if ((f->is_target) && (!built_in_special_target))
+    {
+
+      double itime = invoke_time(f);
+      if (!prif_start)
+        O( fatal, NILF, _("internal error: profile option active, "
+            "but no profile print information found"));
+      if (itime)
+        {
+          prof_info *pprof;
+          for (pprof = prif_start; pprof; pprof = pprof->next)
+            {
+              pprof->info_func(f, (char *)pprof->fmt);
+            }
+          fprintf(stderr, "\n");
+        }
+    }
+}
+
+void
+print_targets_update_time (void)
+{
+  hash_map (&files, print_target_update_time);
+}
+
 /* Verify the integrity of the data base of files.  */
 
 #define VERIFY_CACHED(_p,_n) \
diff --git a/src/filedef.h b/src/filedef.h
index 9d4b816..e8233f9 100644
--- a/src/filedef.h
+++ b/src/filedef.h
@@ -20,6 +20,7 @@ this program.  If not, see <http://www.gnu.org/licenses/>.  */
    All of these are chained together through 'next'.  */
 
 #include "hash.h"
+#include <sys/time.h>
 
 struct file
   {
@@ -76,6 +77,9 @@ struct file
         cs_finished             /* Commands finished.  */
       } command_state ENUM_BITFIELD (2);
 
+    struct timeval t_invoked;
+    struct timeval t_finished;
+
     unsigned int builtin:1;     /* True if the file is a builtin rule. */
     unsigned int precious:1;    /* Non-0 means don't delete file on quit */
     unsigned int loaded:1;      /* True if the file is a loaded object. */
@@ -105,6 +109,15 @@ struct file
 
 extern struct file *default_file;
 
+typedef void (*prof_info_func_t) (const struct file *, char *);
+typedef struct prof_info_st
+  {
+    const char *fmt; /* format string for fprintf */
+    prof_info_func_t info_func;
+    struct prof_info_st *next;
+  } prof_info;
+
+extern prof_info *prif_start;
 
 struct file *lookup_file (const char *name);
 struct file *enter_file (const char *name);
@@ -121,6 +134,16 @@ void verify_file_data_base (void);
 char *build_target_list (char *old_list);
 void print_prereqs (const struct dep *deps);
 void print_file_data_base (void);
+void print_graph (void);
+void print_targets_update_time (void);
+
+void prof_print_str (const struct file *f, char *fmt);
+void prof_print_name (const struct file *f, char *fmt);
+void prof_print_level (const struct file *f, char *fmt);
+void prof_print_pid (const struct file *f, char *fmt);
+void prof_print_invokets (const struct file *f, char *fmt);
+void prof_print_finishts (const struct file *f, char *fmt);
+void prof_print_diff (const struct file *f, char *fmt);
 int try_implicit_rule (struct file *file, unsigned int depth);
 int stemlen_compare (const void *v1, const void *v2);
 
diff --git a/src/main.c b/src/main.c
index bcaeab7..7b90e15 100644
--- a/src/main.c
+++ b/src/main.c
@@ -208,6 +208,18 @@ int ignore_errors_flag = 0;
 
 int print_data_base_flag = 0;
 
+/* Nonzero means don't remake anything, just print the dependency graph
+   that results from reading the makefile (-g).  */
+
+int print_graph_flag = 0;
+
+/* Nonzero means print time stamps for start and finish for each of the
+   targets in the makefile. */
+static struct stringlist *profile_option = 0;
+
+/* Structure keeping the function call list used to print profiling info */
+prof_info *prif_start = 0;
+
 /* Nonzero means don't remake anything; just return a nonzero status
    if the specified targets are not up to date (-q).  */
 
@@ -355,6 +367,9 @@ static const char *const usage[] =
   -f FILE, --file=FILE, --makefile=FILE\n\
                               Read FILE as a makefile.\n"),
     N_("\
+  -g, --graph                 Print make's internal dependency graph\n\
+                              in Graphviz format, without remaking makefiles.\n"),
+    N_("\
   -h, --help                  Print this message and exit.\n"),
     N_("\
   -i, --ignore-errors         Ignore errors from recipes.\n"),
@@ -382,6 +397,10 @@ static const char *const usage[] =
     N_("\
   -p, --print-data-base       Print make's internal database.\n"),
     N_("\
+  -P[FMT], --profile[=FMT], --profile-format[=FMT]\n\
+                              Print profiling information for each target\n\
+                              using the format 'FMT'.\n"),
+    N_("\
   -q, --question              Run no recipe; exit status says if up to date.\n"),
     N_("\
   -r, --no-builtin-rules      Disable the built-in implicit rules.\n"),
@@ -425,6 +444,7 @@ static const struct command_switch switches[] =
     { 'D', flag, &suspend_flag, 1, 1, 0, 0, 0, "suspend-for-debug" },
 #endif
     { 'e', flag, &env_overrides, 1, 1, 0, 0, 0, "environment-overrides", },
+    { 'g', flag, &print_graph_flag, 1, 1, 0, 0, 0, "graph" },
     { 'E', strlist, &eval_strings, 1, 0, 0, 0, 0, "eval" },
     { 'h', flag, &print_usage_flag, 0, 0, 0, 0, 0, "help" },
     { 'i', flag, &ignore_errors_flag, 1, 1, 0, 0, 0, "ignore-errors" },
@@ -456,6 +476,8 @@ static const struct command_switch switches[] =
       &default_load_average, "load-average" },
     { 'o', filename, &old_files, 0, 0, 0, 0, 0, "old-file" },
     { 'O', string, &output_sync_option, 1, 1, 0, "target", 0, "output-sync" },
+    { 'P', strlist, &profile_option, 1, 1, 0, "[PROF:%N:lvl=%L:pid=%P] %S;%E;%D",
+       0, "profile" },
     { 'W', filename, &new_files, 0, 0, 0, 0, 0, "what-if" },
 
     /* These are long-style options.  */
@@ -484,6 +506,7 @@ static struct option long_option_aliases[] =
     { "max-load",       optional_argument,      0, 'l' },
     { "dry-run",        no_argument,            0, 'n' },
     { "recon",          no_argument,            0, 'n' },
+    { "profile-format", optional_argument,      0, 'P' },
     { "makefile",       required_argument,      0, 'f' },
   };
 
@@ -804,6 +827,130 @@ decode_output_sync_flags (void)
 #endif
 }
 
+static const char *
+profile_marker_index(const char *s, int *found)
+{
+  char *ix;
+  const char *ps, *pe;
+
+  *found = 0;
+
+  /* consume all literal strings and '%%' */
+  for (ps = 0; (!ps) || (ix && (*(ix+1) == '%')); ps = ix+2)
+    {
+      if (!ps) ps = s;
+      ix = index(ps, '%');
+      if (ix)
+        {
+          pe = ix;
+          if (*(ix+1) == '\0')
+            {
+              /* XXX: Is this better than returning an error? */
+              *ix = '\0';
+              ix = 0;
+            }
+          else
+            *found = (*(ix+1) != '%');
+        }
+      else
+        {
+          pe = index(ps,'\0');
+        }
+    }
+
+  return pe;
+}
+
+static void
+decode_profile_format (void)
+{
+  const char **pp;
+
+  prof_info **pprif = &prif_start;
+  int found;
+
+  if (!profile_option)
+    return;
+
+  /* Turn the profile format string into a printf format string and list of
+     functions to call to get the values of the parameters for the final
+     string.  */
+
+  for (pp=profile_option->list; *pp; ++pp)
+    {
+      const char *p = *pp;
+      const char *pe;
+
+      do
+        {
+          pe = profile_marker_index(p, &found);
+          /* store literal string */
+          if (p < pe)
+            {
+              /* add the literal string between p and pe */
+              *pprif = calloc(sizeof(prof_info), 1);
+              (*pprif)->fmt = xstrndup(p, (unsigned int)(pe-p));
+              (*pprif)->info_func = prof_print_str;
+              pprif = &((*pprif)->next);
+            }
+          if (found)
+            {
+              prof_info_func_t pfunc = 0;
+              switch (*(pe+1))
+                {
+                  case 'N': pfunc = prof_print_name; pe+=2; break;
+                  case 'L': pfunc = prof_print_level; pe+=2; break;
+                  case 'P': pfunc = prof_print_pid; pe+=2; break;
+                  case 'S': pfunc = prof_print_invokets; pe+=2; break;
+                  case 'E': pfunc = prof_print_finishts; pe+=2; break;
+                  case 'D': pfunc = prof_print_diff; pe+=2; break;
+                  default :
+                            {
+                                char ch[2];
+                                ch[0]=pe[1];
+                                ch[1]='\0';
+                    OS(fatal, NILF,
+                        _("unknown profile information specifier '%s'"),
+                        ch);
+                            }
+                    break;
+                }
+              *pprif = calloc(sizeof(prof_info), 1);
+              (*pprif)->info_func = pfunc;
+              pprif = &((*pprif)->next);
+            }
+          p = pe;
+        }
+      while (*p);
+    }
+}
+
+static void
+free_prof_sublist(prof_info *prof_slist)
+{
+  if (prof_slist)
+    {
+      free_prof_sublist(prof_slist->next);
+      prof_slist->next = 0;
+      if (prof_slist->fmt) free((void*)prof_slist->fmt);
+      prof_slist->fmt = 0;
+      prof_slist->info_func = 0;
+      free(prof_slist);
+    }
+}
+
+static void
+clean_profile_list(void)
+{
+  prof_info **pprif;
+
+  if (!profile_option)
+    return;
+
+  pprif = &prif_start;
+  free_prof_sublist(*pprif);
+}
+
 #ifdef WINDOWS32
 
 #ifndef NO_OUTPUT_SYNC
@@ -2263,7 +2410,10 @@ main (int argc, char **argv, char **envp)
           db_level = DB_NONE;
 
         rebuilding_makefiles = 1;
-        status = update_goal_chain (read_files);
+        if (print_graph_flag)
+          status = -1;
+        else
+          status = update_goal_chain (read_files);
         rebuilding_makefiles = 0;
 
         db_level = orig_db_level;
@@ -2589,28 +2739,34 @@ main (int argc, char **argv, char **envp)
   DB (DB_BASIC, (_("Updating goal targets....\n")));
 
   {
-    switch (update_goal_chain (goals))
-    {
-      case us_none:
-        /* Nothing happened.  */
-        /* FALLTHROUGH */
-      case us_success:
-        /* Keep the previous result.  */
-        break;
-      case us_question:
-        /* We are under -q and would run some commands.  */
-        makefile_status = MAKE_TROUBLE;
-        break;
-      case us_failed:
-        /* Updating failed.  POSIX.2 specifies exit status >1 for this; */
-        makefile_status = MAKE_FAILURE;
-        break;
-    }
+    if (print_graph_flag)
+      makefile_status=0;
+    else
+      {
+        switch (update_goal_chain (goals))
+        {
+          case us_none:
+            /* Nothing happened.  */
+            /* FALLTHROUGH */
+          case us_success:
+            /* Keep the previous result.  */
+            break;
+          case us_question:
+            /* We are under -q and would run some commands.  */
+            makefile_status = MAKE_TROUBLE;
+            break;
+          case us_failed:
+            /* Updating failed.  POSIX.2 specifies exit status >1 for this;
+               but in VMS, there is only success and failure.  */
+            makefile_status = MAKE_FAILURE;
+            break;
+        }
 
-    /* If we detected some clock skew, generate one last warning */
-    if (clock_skew_detected)
-      O (error, NILF,
-         _("warning:  Clock skew detected.  Your build may be incomplete."));
+        /* If we detected some clock skew, generate one last warning */
+        if (clock_skew_detected)
+          O (error, NILF,
+                 _("warning:  Clock skew detected.  Your build may be incomplete."));
+      }
 
     /* Exit.  */
     die (makefile_status);
@@ -2619,7 +2775,7 @@ main (int argc, char **argv, char **envp)
   /* NOTREACHED */
   exit (MAKE_SUCCESS);
 }
-
+
 /* Parsing of arguments, decoding of switches.  */
 
 static char options[1 + sizeof (switches) / sizeof (switches[0]) * 3];
@@ -3011,6 +3167,7 @@ decode_switches (int argc, const char **argv, int env)
   /* If there are any options that need to be decoded do it now.  */
   decode_debug_flags ();
   decode_output_sync_flags ();
+  decode_profile_format ();
 }
 
 /* Decode switches from environment variable ENVAR (which is LEN chars long).
@@ -3082,7 +3239,7 @@ decode_env_switches (const char *envar, size_t len)
   /* Parse those words.  */
   decode_switches (argc, argv, 1);
 }
-
+
 /* Quote the string IN so that it will be interpreted as a single word with
    no magic by decode_env_switches; also double dollar signs to avoid
    variable expansion in make itself.  Write the result into OUT, returning
@@ -3342,7 +3499,7 @@ define_makeflags (int all, int makefile)
   return define_variable_cname ("MAKEFLAGS", flagstring,
                                 env_overrides ? o_env_override : o_file, 1);
 }
-
+
 /* Print version information.  */
 
 static void
@@ -3350,7 +3507,7 @@ print_version (void)
 {
   static int printed_version = 0;
 
-  const char *precede = print_data_base_flag ? "# " : "";
+  const char *precede = (print_data_base_flag||print_graph_flag) ? "# " : "";
 
   if (printed_version)
     /* Do it only once.  */
@@ -3442,7 +3599,7 @@ clean_jobserver (int status)
       reset_jobserver ();
     }
 }
-
+
 /* Exit with STATUS, cleaning up as necessary.  */
 
 void
@@ -3473,9 +3630,16 @@ die (int status)
       if (print_data_base_flag)
         print_data_base ();
 
+      if (print_graph_flag)
+        print_graph ();
+
+      if (profile_option)
+        print_targets_update_time ();
+
       if (verify_flag)
         verify_file_data_base ();
 
+      clean_profile_list();
       clean_jobserver (status);
 
       if (output_context)
diff --git a/src/remake.c b/src/remake.c
index 8c888b6..cf8ce08 100644
--- a/src/remake.c
+++ b/src/remake.c
@@ -873,6 +873,7 @@ notice_finished_file (struct file *file)
   int touched = 0;
 
   file->command_state = cs_finished;
+  gettimeofday (&file->t_finished, NULL);
   file->updated = 1;
 
   if (touch_flag
@@ -1207,6 +1208,7 @@ touch_file (struct file *file)
 static void
 remake_file (struct file *file)
 {
+  gettimeofday (&file->t_invoked, NULL);
   if (file->cmds == 0)
     {
       if (file->phony)
-- 
2.21.0

