Commit 240f4af321612a0fe4cf01aed75a8cb44173feb8

Russell Belfer 2014-04-28T14:04:29

Add build option for diff internal statistics

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 884c9bc..83e03d6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -37,6 +37,7 @@ OPTION( ANDROID				"Build for android NDK"	 				OFF )
 OPTION( USE_ICONV			"Link with and use iconv library" 		OFF )
 OPTION( USE_SSH				"Link with libssh to enable SSH support" ON )
 OPTION( VALGRIND			"Configure build for valgrind"			OFF )
+OPTION( PERF_STATS			"Internally track performance data"		OFF )
 
 IF(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
 	SET( USE_ICONV ON )
@@ -352,6 +353,10 @@ IF (THREADSAFE)
 	ADD_DEFINITIONS(-DGIT_THREADS)
 ENDIF()
 
+IF (PERF_STATS)
+	ADD_DEFINITIONS(-DGIT_PERF)
+ENDIF()
+
 ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64)
 
 # Collect sourcefiles
diff --git a/src/checkout.c b/src/checkout.c
index bc976b8..93d6bc9 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -185,8 +185,7 @@ static bool checkout_is_workdir_modified(
 		return true;
 
 	if (git_diff__oid_for_file(
-			data->repo, wditem->path, wditem->mode,
-			wditem->file_size, &oid) < 0)
+			&oid, data->diff, wditem->path, wditem->mode, wditem->file_size) < 0)
 		return false;
 
 	return (git_oid__cmp(&baseitem->id, &oid) != 0);
diff --git a/src/diff.c b/src/diff.c
index 4b6fbe2..aa88065 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -510,26 +510,31 @@ void git_diff_addref(git_diff *diff)
 }
 
 int git_diff__oid_for_file(
-	git_repository *repo,
+	git_oid *out,
+	git_diff *diff,
 	const char *path,
 	uint16_t  mode,
-	git_off_t size,
-	git_oid *oid)
+	git_off_t size)
 {
-	int result = 0;
+	int error = 0;
 	git_buf full_path = GIT_BUF_INIT;
+	git_filter_list *fl = NULL;
+
+	memset(out, 0, sizeof(*out));
 
 	if (git_buf_joinpath(
-		&full_path, git_repository_workdir(repo), path) < 0)
+		&full_path, git_repository_workdir(diff->repo), path) < 0)
 		return -1;
 
 	if (!mode) {
 		struct stat st;
 
-		if (p_stat(path, &st) < 0) {
-			giterr_set(GITERR_OS, "Could not stat '%s'", path);
-			result = -1;
-			goto cleanup;
+		GIT_PERF_INC(diff->stat_calls);
+
+		if (p_stat(full_path.ptr, &st) < 0) {
+			error = git_path_set_error(errno, path, "stat");
+			git_buf_free(&full_path);
+			return error;
 		}
 
 		mode = st.st_mode;
@@ -540,46 +545,43 @@ int git_diff__oid_for_file(
 	if (S_ISGITLINK(mode)) {
 		git_submodule *sm;
 
-		memset(oid, 0, sizeof(*oid));
+		GIT_PERF_INC(diff->submodule_lookups);
 
-		if (!git_submodule_lookup(&sm, repo, path)) {
+		if (!git_submodule_lookup(&sm, diff->repo, path)) {
 			const git_oid *sm_oid = git_submodule_wd_id(sm);
 			if (sm_oid)
-				git_oid_cpy(oid, sm_oid);
+				git_oid_cpy(out, sm_oid);
 			git_submodule_free(sm);
 		} else {
 			/* if submodule lookup failed probably just in an intermediate
 			 * state where some init hasn't happened, so ignore the error
 			 */
 			giterr_clear();
-			memset(oid, 0, sizeof(*oid));
 		}
 	} else if (S_ISLNK(mode)) {
-		result = git_odb__hashlink(oid, full_path.ptr);
+		GIT_PERF_INC(diff->oid_calculations);
+		error = git_odb__hashlink(out, full_path.ptr);
 	} else if (!git__is_sizet(size)) {
 		giterr_set(GITERR_OS, "File size overflow (for 32-bits) on '%s'", path);
-		result = -1;
-	} else {
-		git_filter_list *fl = NULL;
-
-		result = git_filter_list_load(&fl, repo, NULL, path, GIT_FILTER_TO_ODB);
-		if (!result) {
-			int fd = git_futils_open_ro(full_path.ptr);
-			if (fd < 0)
-				result = fd;
-			else {
-				result = git_odb__hashfd_filtered(
-					oid, fd, (size_t)size, GIT_OBJ_BLOB, fl);
-				p_close(fd);
-			}
-
-			git_filter_list_free(fl);
+		error = -1;
+	} else if (!(error = git_filter_list_load(
+			&fl, diff->repo, NULL, path, GIT_FILTER_TO_ODB)))
+	{
+		int fd = git_futils_open_ro(full_path.ptr);
+		if (fd < 0)
+			error = fd;
+		else {
+			GIT_PERF_INC(diff->oid_calculations);
+			error = git_odb__hashfd_filtered(
+				out, fd, (size_t)size, GIT_OBJ_BLOB, fl);
+			p_close(fd);
 		}
+
+		git_filter_list_free(fl);
 	}
 
-cleanup:
 	git_buf_free(&full_path);
-	return result;
+	return error;
 }
 
 static bool diff_time_eq(
@@ -617,6 +619,8 @@ static int maybe_modified_submodule(
 		ign == GIT_SUBMODULE_IGNORE_ALL)
 		return 0;
 
+	GIT_PERF_INC(diff->submodule_lookups);
+
 	if ((error = git_submodule_lookup(
 			&sub, diff->repo, info->nitem->path)) < 0) {
 
@@ -748,8 +752,8 @@ static int maybe_modified(
 	 */
 	if (status == GIT_DELTA_MODIFIED && git_oid_iszero(&nitem->id)) {
 		if (git_oid_iszero(&noid)) {
-			if ((error = git_diff__oid_for_file(diff->repo,
-					nitem->path, nitem->mode, nitem->file_size, &noid)) < 0)
+			if ((error = git_diff__oid_for_file(&noid,
+					diff, nitem->path, nitem->mode, nitem->file_size)) < 0)
 				return error;
 		}
 
@@ -914,6 +918,8 @@ static int handle_unmatched_new_item(
 		delta_type = GIT_DELTA_ADDED;
 
 	else if (nitem->mode == GIT_FILEMODE_COMMIT) {
+		GIT_PERF_INC(diff->submodule_lookups);
+
 		/* ignore things that are not actual submodules */
 		if (git_submodule_lookup(NULL, info->repo, nitem->path) != 0) {
 			giterr_clear();
@@ -1066,6 +1072,11 @@ int git_diff__from_iterators(
 			error = 0;
 	}
 
+	GIT_PERF_ADD(diff->stat_calls, old_iter->stat_calls);
+	GIT_PERF_ADD(diff->stat_calls, new_iter->stat_calls);
+	GIT_PERF_ADD(diff->submodule_lookups, old_iter->submodule_lookups);
+	GIT_PERF_ADD(diff->submodule_lookups, new_iter->submodule_lookups);
+
 cleanup:
 	if (!error)
 		*diff_ptr = diff;
diff --git a/src/diff.h b/src/diff.h
index aae8fbf..491fc46 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -62,6 +62,11 @@ struct git_diff {
 	git_iterator_type_t old_src;
 	git_iterator_type_t new_src;
 	uint32_t diffcaps;
+#ifdef GIT_PERF
+	size_t stat_calls;
+	size_t oid_calculations;
+	size_t submodule_lookups;
+#endif
 
 	int (*strcomp)(const char *, const char *);
 	int (*strncomp)(const char *, const char *, size_t);
@@ -90,7 +95,7 @@ extern int git_diff_delta__format_file_header(
 	int oid_strlen);
 
 extern int git_diff__oid_for_file(
-	git_repository *, const char *, uint16_t, git_off_t, git_oid *);
+	git_oid *oit, git_diff *, const char *, uint16_t, git_off_t);
 
 extern int git_diff__from_iterators(
 	git_diff **diff_ptr,
diff --git a/src/diff_tform.c b/src/diff_tform.c
index 97fbc28..a2dab0a 100644
--- a/src/diff_tform.c
+++ b/src/diff_tform.c
@@ -574,14 +574,14 @@ static int similarity_measure(
 	if (exact_match) {
 		if (git_oid_iszero(&a_file->id) &&
 			diff->old_src == GIT_ITERATOR_TYPE_WORKDIR &&
-			!git_diff__oid_for_file(diff->repo, a_file->path,
-				a_file->mode, a_file->size, &a_file->id))
+			!git_diff__oid_for_file(&a_file->id,
+				diff, a_file->path, a_file->mode, a_file->size))
 			a_file->flags |= GIT_DIFF_FLAG_VALID_ID;
 
 		if (git_oid_iszero(&b_file->id) &&
 			diff->new_src == GIT_ITERATOR_TYPE_WORKDIR &&
-			!git_diff__oid_for_file(diff->repo, b_file->path,
-				b_file->mode, b_file->size, &b_file->id))
+			!git_diff__oid_for_file(&b_file->id,
+				diff, b_file->path, b_file->mode, b_file->size))
 			b_file->flags |= GIT_DIFF_FLAG_VALID_ID;
 	}
 
diff --git a/src/iterator.c b/src/iterator.c
index ef27fa7..5e668b5 100644
--- a/src/iterator.c
+++ b/src/iterator.c
@@ -1017,6 +1017,8 @@ static int fs_iterator__expand_dir(fs_iterator *fi)
 		return GIT_ENOTFOUND;
 	}
 
+	GIT_PERF_ADD(fi->base.stat_calls, ff->entries.length);
+
 	fs_iterator__seek_frame_start(fi, ff);
 
 	ff->next  = fi->stack;
@@ -1304,9 +1306,11 @@ static int workdir_iterator__enter_dir(fs_iterator *fi)
 
 	/* convert submodules to GITLINK and remove trailing slashes */
 	git_vector_foreach(&ff->entries, pos, entry) {
-		if (S_ISDIR(entry->st.st_mode) &&
-			git_submodule__is_submodule(fi->base.repo, entry->path))
-		{
+		if (!S_ISDIR(entry->st.st_mode))
+			continue;
+
+		GIT_PERF_INC(fi->base.submodule_lookups);
+		if (git_submodule__is_submodule(fi->base.repo, entry->path)) {
 			entry->st.st_mode = GIT_FILEMODE_COMMIT;
 			entry->path_len--;
 			entry->path[entry->path_len] = '\0';
diff --git a/src/iterator.h b/src/iterator.h
index ba9c1e4..2968b8c 100644
--- a/src/iterator.h
+++ b/src/iterator.h
@@ -53,6 +53,10 @@ struct git_iterator {
 	char *end;
 	int (*prefixcomp)(const char *str, const char *prefix);
 	unsigned int flags;
+#ifdef GIT_PERF
+	size_t stat_calls;
+	size_t submodule_lookups;
+#endif
 };
 
 extern int git_iterator_for_nothing(
diff --git a/src/status.c b/src/status.c
index c4b990a..e1f8e06 100644
--- a/src/status.c
+++ b/src/status.c
@@ -81,15 +81,15 @@ static unsigned int workdir_delta2status(
 			if (git_oid_iszero(&idx2wd->old_file.id) &&
 				diff->old_src == GIT_ITERATOR_TYPE_WORKDIR &&
 				!git_diff__oid_for_file(
-					diff->repo, idx2wd->old_file.path, idx2wd->old_file.mode,
-					idx2wd->old_file.size, &idx2wd->old_file.id))
+					&idx2wd->old_file.id, diff, idx2wd->old_file.path,
+					idx2wd->old_file.mode, idx2wd->old_file.size))
 			idx2wd->old_file.flags |= GIT_DIFF_FLAG_VALID_ID;
 
 			if (git_oid_iszero(&idx2wd->new_file.id) &&
 				diff->new_src == GIT_ITERATOR_TYPE_WORKDIR &&
 				!git_diff__oid_for_file(
-					diff->repo, idx2wd->new_file.path, idx2wd->new_file.mode,
-					idx2wd->new_file.size, &idx2wd->new_file.id))
+					&idx2wd->new_file.id, diff, idx2wd->new_file.path,
+					idx2wd->new_file.mode, idx2wd->new_file.size))
 				idx2wd->new_file.flags |= GIT_DIFF_FLAG_VALID_ID;
 
 			if (!git_oid_equal(&idx2wd->old_file.id, &idx2wd->new_file.id))
diff --git a/src/util.h b/src/util.h
index 6fb2dc0..be7a16e 100644
--- a/src/util.h
+++ b/src/util.h
@@ -436,4 +436,12 @@ GIT_INLINE(double) git__timer(void)
 
 #endif
 
+#ifdef GIT_PERF
+#	define GIT_PERF_INC(counter) (counter)++
+#	define GIT_PERF_ADD(counter,val) (counter) += (val)
+#else
+#	define GIT_PERF_INC(counter) 0
+#	define GIT_PERF_ADD(counter,val) 0
+#endif
+
 #endif /* INCLUDE_util_h__ */
diff --git a/tests/diff/workdir.c b/tests/diff/workdir.c
index 6128e82..03a3ff4 100644
--- a/tests/diff/workdir.c
+++ b/tests/diff/workdir.c
@@ -2,6 +2,11 @@
 #include "diff_helpers.h"
 #include "repository.h"
 
+#ifdef GIT_PERF
+/* access to diff usage statistics */
+#	include "diff.h"
+#endif
+
 static git_repository *g_repo = NULL;
 
 void test_diff_workdir__initialize(void)
@@ -58,6 +63,13 @@ void test_diff_workdir__to_index(void)
 		cl_assert_equal_i(5, exp.line_ctxt);
 		cl_assert_equal_i(4, exp.line_adds);
 		cl_assert_equal_i(5, exp.line_dels);
+
+#ifdef GIT_PERF
+		cl_assert_equal_sz(
+			13 /* in root */ + 3 /* in subdir */, diff->stat_calls);
+		cl_assert_equal_sz(9, diff->oid_calculations);
+		cl_assert_equal_sz(2, diff->submodule_lookups);
+#endif
 	}
 
 	git_diff_free(diff);