diff --git a/file.c b/file.c
index 92c4052..99c8431 100644
--- a/file.c
+++ b/file.c
@@ -1049,6 +1049,116 @@ print_file_data_base (void)
   hash_print_stats (&files, stdout);
 }
 
+
+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, 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/filedef.h b/filedef.h
index f458551..e1890dc 100644
--- a/filedef.h
+++ b/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
   {
@@ -74,6 +75,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 *suffix_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);
@@ -120,6 +133,15 @@ void init_hash_files (void);
 char *build_target_list (char *old_list);
 void print_prereqs (const struct dep *deps);
 void print_file_data_base (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);
 
 #if FILE_TIMESTAMP_HI_RES
 # define FILE_TIMESTAMP_STAT_MODTIME(fname, st) \
diff --git a/main.c b/main.c
index 91b6c2d..597411b 100644
--- a/main.c
+++ b/main.c
@@ -180,6 +180,14 @@ int ignore_errors_flag = 0;
 
 int print_data_base_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).  */
 
@@ -358,6 +366,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"),
@@ -434,6 +446,8 @@ static const struct command_switch switches[] =
 #endif
     { '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.  */
@@ -461,6 +475,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' },
   };
 
@@ -777,6 +792,125 @@ decode_output_sync_flags (void)
     RECORD_SYNC_MUTEX (sync_mutex);
 }
 
+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 :
+                    OS(fatal, NILF,
+                        _("unknown profile information specifier '%c'"),
+                        *(pe+1));
+                    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
@@ -2893,6 +3027,7 @@ decode_switches (int argc, 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).
@@ -3400,9 +3535,13 @@ die (int status)
       if (print_data_base_flag)
         print_data_base ();
 
+      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/make.1 b/make.1
index 94536fe..2216ba1 100644
--- a/make.1
+++ b/make.1
@@ -1,4 +1,4 @@
-.TH MAKE 1 "03 March 2012" "GNU" "User Commands"
+.TH MAKE 1 "19 Nov 2012" "GNU" "User Commands"
 .SH NAME
 make \- GNU make utility to maintain groups of programs
 .SH SYNOPSIS
@@ -254,6 +254,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/remake.c b/remake.c
index ff22277..34f8104 100644
--- a/remake.c
+++ b/remake.c
@@ -860,6 +860,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
@@ -1184,6 +1185,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)
