use time.Since instead of time.Now().Sub

count # of shell invocation.

$ repo/android.sh kati -c -kati_stats
...
*kati*: eval time: "59.179409898s"
*kati*: shell func time: "40.99470661s" 2896
...

note that # of unique shell command lines seems to be 2514.
 from "$ repo/android.sh kati -c -kati_eval_stats"
 grep shell: | wc -l

top $(shell ..); cumulative time > 1sec
 from "$ repo/android.sh kati -c -kati_eval_stats"

count,longest(ns),total(ns),longest,total,name
...
1325,53941610,16717939038,53.94161ms,16.717939038s,func:$(shell if [ -d $1 ] ; then cd $1 ; find ./ -not -name '.*' -and -type f -and -not -type l ; fi)
...
922,37729941,13098517289,37.729941ms,13.098517289s,func:$(shell cd ${LOCAL_PATH} ; find -L $1 -name "*.java" -and -not -name ".*")
...
162,39814484,2716881896,39.814484ms,2.716881896s,func:$(shell cd ${TOP_DIR}${LOCAL_PATH}/${dir} && find . -type d -a -name ".svn" -prune -o -type f -a \! -name"*.java" -a \! -name "package.html" -a \! -name "overview.html" -a \! -name ".*.swp" -a \! -name ".DS_Store" -a \! -name "*~" -print )
...
174,34637725,2245014340,34.637725ms,2.24501434s,func:$(shell echo $1 | tr 'a-zA-Z' 'n-za-mN-ZA-M')
...
1,1633210877,1633210877,1.633210877s,1.633210877s,func:$(shell build/tools/findleaves.py --prune=${OUT_DIR} --prune=.repo --prune=.git . CleanSpec.mk)
...

note:
$ time find repo/android -ls > /dev/null
find repo/android -ls > /dev/null  2.33s user 2.04s system 100% cpu 4.366 total
diff --git a/func.go b/func.go
index 6de6118..855ced4 100644
--- a/func.go
+++ b/func.go
@@ -678,7 +678,10 @@
 // http://www.gnu.org/software/make/manual/make.html#Shell-Function
 type funcShell struct{ fclosure }
 
-var shellFuncTime time.Duration
+var (
+	shellFuncTime  time.Duration
+	shellFuncCount int
+)
 
 func (f *funcShell) Arity() int { return 1 }
 
@@ -704,10 +707,13 @@
 	abuf := newBuf()
 	f.args[1].Eval(abuf, ev)
 	if ev.avoidIO && !hasNoIoInShellScript(abuf.Bytes()) {
+		t := time.Now()
 		ev.hasIO = true
 		w.Write([]byte("$("))
 		w.Write(abuf.Bytes())
 		w.Write([]byte{')'})
+		addStats("shell", tmpval(abuf.Bytes()), t)
+		freeBuf(abuf)
 		return
 	}
 	arg := abuf.String()
@@ -722,12 +728,13 @@
 	}
 	t := time.Now()
 	out, err := cmd.Output()
-	shellFuncTime += time.Now().Sub(t)
+	shellFuncTime += time.Since(t)
+	shellFuncCount++
 	if err != nil {
 		Logf("$(shell %q) failed: %q", arg, err)
 	}
-
 	w.Write(formatCommandOutput(out))
+	addStats("shell", literal(arg), t)
 }
 
 // https://www.gnu.org/software/make/manual/html_node/Call-Function.html#Call-Function
diff --git a/main.go b/main.go
index b4157cb..457c5ff 100644
--- a/main.go
+++ b/main.go
@@ -145,12 +145,12 @@
 
 	if loadGOB != "" {
 		g := LoadDepGraph(loadGOB)
-		LogStats("deserialize time: %q", time.Now().Sub(startTime))
+		LogStats("deserialize time: %q", time.Since(startTime))
 		return g
 	}
 	if loadJSON != "" {
 		g := LoadDepGraphFromJSON(loadJSON)
-		LogStats("deserialize time: %q", time.Now().Sub(startTime))
+		LogStats("deserialize time: %q", time.Since(startTime))
 		return g
 	}
 
@@ -215,19 +215,19 @@
 
 	vars.Merge(er.vars)
 
-	LogStats("eval time: %q", time.Now().Sub(startTime))
-	LogStats("shell func time: %q", shellFuncTime)
+	LogStats("eval time: %q", time.Since(startTime))
+	LogStats("shell func time: %q %d", shellFuncTime, shellFuncCount)
 
 	startTime = time.Now()
 	db := NewDepBuilder(er, vars)
-	LogStats("dep build prepare time: %q", time.Now().Sub(startTime))
+	LogStats("dep build prepare time: %q", time.Since(startTime))
 
 	startTime = time.Now()
 	nodes, err2 := db.Eval(targets)
 	if err2 != nil {
 		panic(err2)
 	}
-	LogStats("dep build time: %q", time.Now().Sub(startTime))
+	LogStats("dep build time: %q", time.Since(startTime))
 	var readMks []*ReadMakefile
 	// Always put the root Makefile as the first element.
 	readMks = append(readMks, &ReadMakefile{
@@ -300,30 +300,30 @@
 	if eagerCmdEvalFlag {
 		startTime := time.Now()
 		EvalCommands(nodes, vars)
-		LogStats("eager eval command time: %q", time.Now().Sub(startTime))
+		LogStats("eager eval command time: %q", time.Since(startTime))
 	}
 
 	if saveGOB != "" {
 		startTime := time.Now()
 		DumpDepGraph(g, saveGOB, targets)
-		LogStats("serialize time: %q", time.Now().Sub(startTime))
+		LogStats("serialize time: %q", time.Since(startTime))
 	}
 	if saveJSON != "" {
 		startTime := time.Now()
 		DumpDepGraphAsJSON(g, saveJSON, targets)
-		LogStats("serialize time: %q", time.Now().Sub(startTime))
+		LogStats("serialize time: %q", time.Since(startTime))
 	}
 
 	if useCache && !g.isCached {
 		startTime := time.Now()
 		DumpDepGraphCache(g, targets)
-		LogStats("serialize time: %q", time.Now().Sub(startTime))
+		LogStats("serialize time: %q", time.Since(startTime))
 	}
 
 	if generateNinja {
 		startTime := time.Now()
 		GenerateNinja(g)
-		LogStats("generate ninja time: %q", time.Now().Sub(startTime))
+		LogStats("generate ninja time: %q", time.Since(startTime))
 		return
 	}
 
@@ -352,5 +352,5 @@
 	if err != nil {
 		panic(err)
 	}
-	LogStats("exec time: %q", time.Now().Sub(startTime))
+	LogStats("exec time: %q", time.Since(startTime))
 }
diff --git a/serialize.go b/serialize.go
index d9f2d94..46bbbf5 100644
--- a/serialize.go
+++ b/serialize.go
@@ -252,10 +252,10 @@
 	e := gob.NewEncoder(f)
 	startTime := time.Now()
 	sg := MakeSerializableGraph(g, roots)
-	LogStats("serialize prepare time: %q", time.Now().Sub(startTime))
+	LogStats("serialize prepare time: %q", time.Since(startTime))
 	startTime = time.Now()
 	e.Encode(sg)
-	LogStats("serialize output time: %q", time.Now().Sub(startTime))
+	LogStats("serialize output time: %q", time.Since(startTime))
 	err = f.Close()
 	if err != nil {
 		panic(err)
@@ -586,7 +586,7 @@
 func LoadDepGraphCache(makefile string, roots []string) *DepGraph {
 	startTime := time.Now()
 	defer func() {
-		LogStats("Cache lookup time: %q", time.Now().Sub(startTime))
+		LogStats("Cache lookup time: %q", time.Since(startTime))
 	}()
 
 	filename := GetCacheFilename(makefile, roots)