lib/checkout: Optimize checkout by avoiding OstreeRepoFile recusion

Looking at `perf record ostree checkout`, some things stand out; e.g.:

```
+   27.63%     0.07%  ostree   libgio-2.0.so.0.5000.3      [.] g_file_enumerator_iterate
+   22.74%     0.28%  ostree   libostree-1.so.1.0.0        [.] ostree_repo_file_tree_query_child
+   13.74%     0.08%  ostree   libostree-1.so.1.0.0        [.] ot_variant_bsearch_str
```

The GIO abstractions are already fairly heavyweight, and `OstreeRepoFile` mallocs
a lot too.

Make things more efficient here by dropping the GIO bits for reading ostree data -
we just read from the variants directly and iterate over them.  The end result
here is that according to perf we go from ~40% of our time in the kernel to
~70%, and things like `g_file_enumerator_iterate()` drop entirely out of the
hot set.

Closes: #848
Approved by: jlebon
This commit is contained in:
Colin Walters 2017-05-10 21:40:50 -04:00 committed by Atomic Bot
parent 7896bcbe65
commit e6f17b949d
2 changed files with 97 additions and 64 deletions

View File

@ -90,6 +90,14 @@ _ostree_make_temporary_symlink_at (int tmp_dirfd,
GFileInfo * _ostree_header_gfile_info_new (mode_t mode, uid_t uid, gid_t gid); GFileInfo * _ostree_header_gfile_info_new (mode_t mode, uid_t uid, gid_t gid);
static inline void
_ostree_checksum_inplace_from_bytes_v (GVariant *csum_v, char *buf)
{
const guint8*csum = ostree_checksum_bytes_peek (csum_v);
g_assert (csum);
ostree_checksum_inplace_from_bytes (csum, buf);
}
/* XX/checksum-2.extension, but let's just use 256 for a /* XX/checksum-2.extension, but let's just use 256 for a
* bit of overkill. * bit of overkill.
*/ */

View File

@ -361,8 +361,7 @@ static gboolean
checkout_one_file_at (OstreeRepo *repo, checkout_one_file_at (OstreeRepo *repo,
OstreeRepoCheckoutAtOptions *options, OstreeRepoCheckoutAtOptions *options,
CheckoutState *state, CheckoutState *state,
GFile *source, const char *checksum,
GFileInfo *source_info,
int destination_dfd, int destination_dfd,
const char *destination_name, const char *destination_name,
GCancellable *cancellable, GCancellable *cancellable,
@ -371,8 +370,14 @@ checkout_one_file_at (OstreeRepo *repo,
gboolean need_copy = TRUE; gboolean need_copy = TRUE;
gboolean is_bare_user_symlink = FALSE; gboolean is_bare_user_symlink = FALSE;
char loose_path_buf[_OSTREE_LOOSE_PATH_MAX]; char loose_path_buf[_OSTREE_LOOSE_PATH_MAX];
/* FIXME - avoid the GFileInfo here */
g_autoptr(GFileInfo) source_info = NULL;
if (!ostree_repo_load_file (repo, checksum, NULL, &source_info, NULL,
cancellable, error))
return FALSE;
const gboolean is_symlink = (g_file_info_get_file_type (source_info) == G_FILE_TYPE_SYMBOLIC_LINK); const gboolean is_symlink = (g_file_info_get_file_type (source_info) == G_FILE_TYPE_SYMBOLIC_LINK);
const char *checksum = ostree_repo_file_get_checksum ((OstreeRepoFile*)source);
const gboolean is_whiteout = (!is_symlink && options->process_whiteouts && const gboolean is_whiteout = (!is_symlink && options->process_whiteouts &&
g_str_has_prefix (destination_name, WHITEOUT_PREFIX)); g_str_has_prefix (destination_name, WHITEOUT_PREFIX));
@ -592,21 +597,33 @@ checkout_tree_at_recurse (OstreeRepo *self,
CheckoutState *state, CheckoutState *state,
int destination_parent_fd, int destination_parent_fd,
const char *destination_name, const char *destination_name,
OstreeRepoFile *source, const char *dirtree_checksum,
GFileInfo *source_info, const char *dirmeta_checksum,
GCancellable *cancellable, GCancellable *cancellable,
GError **error) GError **error)
{ {
gboolean did_exist = FALSE; gboolean did_exist = FALSE;
const gboolean sepolicy_enabled = options->sepolicy && !self->disable_xattrs; const gboolean sepolicy_enabled = options->sepolicy && !self->disable_xattrs;
g_autoptr(GVariant) dirtree = NULL;
g_autoptr(GVariant) dirmeta = NULL;
g_autoptr(GVariant) xattrs = NULL; g_autoptr(GVariant) xattrs = NULL;
g_autoptr(GVariant) modified_xattrs = NULL; g_autoptr(GVariant) modified_xattrs = NULL;
if (options->mode != OSTREE_REPO_CHECKOUT_MODE_USER) if (!ostree_repo_load_variant (self, OSTREE_OBJECT_TYPE_DIR_TREE,
{ dirtree_checksum, &dirtree, error))
if (!ostree_repo_file_get_xattrs (source, &xattrs, NULL, error)) return FALSE;
return FALSE; if (!ostree_repo_load_variant (self, OSTREE_OBJECT_TYPE_DIR_META,
} dirmeta_checksum, &dirmeta, error))
return FALSE;
/* Parse OSTREE_OBJECT_TYPE_DIR_META */
guint32 uid, gid, mode;
g_variant_get (dirmeta, "(uuu@a(ayay))",
&uid, &gid, &mode,
options->mode != OSTREE_REPO_CHECKOUT_MODE_USER ? &xattrs : NULL);
uid = GUINT32_FROM_BE (uid);
gid = GUINT32_FROM_BE (gid);
mode = GUINT32_FROM_BE (mode);
/* First, make the directory. Push a new scope in case we end up using /* First, make the directory. Push a new scope in case we end up using
* setfscreatecon(). * setfscreatecon().
@ -623,8 +640,7 @@ checkout_tree_at_recurse (OstreeRepo *self,
if (!_ostree_sepolicy_preparefscreatecon (&fscreatecon, options->sepolicy, if (!_ostree_sepolicy_preparefscreatecon (&fscreatecon, options->sepolicy,
state->selabel_path_buf->str, state->selabel_path_buf->str,
g_file_info_get_attribute_uint32 (source_info, "unix::mode"), mode, error))
error))
return FALSE; return FALSE;
} }
@ -666,72 +682,78 @@ checkout_tree_at_recurse (OstreeRepo *self,
return FALSE; return FALSE;
} }
g_autoptr(GFileEnumerator) dir_enum = GString *selabel_path_buf = state->selabel_path_buf;
g_file_enumerate_children ((GFile*)source, /* Process files in this subdir */
OSTREE_GIO_FAST_QUERYINFO, { g_autoptr(GVariant) dir_file_contents = g_variant_get_child_value (dirtree, 0);
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, GVariantIter viter;
cancellable, g_variant_iter_init (&viter, dir_file_contents);
error); const char *fname;
if (!dir_enum) g_autoptr(GVariant) contents_csum_v = NULL;
return FALSE; while (g_variant_iter_loop (&viter, "(&s@ay)", &fname, &contents_csum_v))
{
const size_t namelen = strlen (fname);
if (selabel_path_buf)
g_string_append_len (selabel_path_buf, fname, namelen);
while (TRUE) char tmp_checksum[OSTREE_SHA256_STRING_LEN+1];
{ _ostree_checksum_inplace_from_bytes_v (contents_csum_v, tmp_checksum);
GFileInfo *file_info;
GFile *src_child;
const char *name;
GString *selabel_path_buf = state->selabel_path_buf;
if (!g_file_enumerator_iterate (dir_enum, &file_info, &src_child, if (!checkout_one_file_at (self, options, state,
cancellable, error)) tmp_checksum,
return FALSE; destination_dfd, fname,
if (file_info == NULL) cancellable, error))
break; return FALSE;
name = g_file_info_get_name (file_info); if (selabel_path_buf)
size_t namelen = strlen (name); g_string_truncate (selabel_path_buf, selabel_path_buf->len - namelen);
if (selabel_path_buf) }
g_string_append_len (selabel_path_buf, name, namelen); contents_csum_v = NULL; /* iter_loop freed it */
}
if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY) /* Process subdirectories */
{ { g_autoptr(GVariant) dir_subdirs = g_variant_get_child_value (dirtree, 1);
if (selabel_path_buf) const char *dname;
g_autoptr(GVariant) subdirtree_csum_v = NULL;
g_autoptr(GVariant) subdirmeta_csum_v = NULL;
GVariantIter viter;
g_variant_iter_init (&viter, dir_subdirs);
while (g_variant_iter_loop (&viter, "(&s@ay@ay)", &dname,
&subdirtree_csum_v, &subdirmeta_csum_v))
{
const size_t namelen = strlen (dname);
if (selabel_path_buf)
{
g_string_append_len (selabel_path_buf, dname, namelen);
g_string_append_c (selabel_path_buf, '/'); g_string_append_c (selabel_path_buf, '/');
if (!checkout_tree_at_recurse (self, options, state, }
destination_dfd, name,
(OstreeRepoFile*)src_child, file_info,
cancellable, error))
return FALSE;
if (state->selabel_path_buf)
g_string_truncate (selabel_path_buf, state->selabel_path_buf->len-1);
}
else
{
if (!checkout_one_file_at (self, options, state,
src_child, file_info,
destination_dfd, name,
cancellable, error))
return FALSE;
}
if (selabel_path_buf) char subdirtree_checksum[OSTREE_SHA256_STRING_LEN+1];
g_string_truncate (selabel_path_buf, selabel_path_buf->len - namelen); _ostree_checksum_inplace_from_bytes_v (subdirtree_csum_v, subdirtree_checksum);
} char subdirmeta_checksum[OSTREE_SHA256_STRING_LEN+1];
_ostree_checksum_inplace_from_bytes_v (subdirmeta_csum_v, subdirmeta_checksum);
if (!checkout_tree_at_recurse (self, options, state,
destination_dfd, dname,
subdirtree_checksum, subdirmeta_checksum,
cancellable, error))
return FALSE;
if (selabel_path_buf)
g_string_truncate (selabel_path_buf, selabel_path_buf->len - namelen);
}
}
/* We do fchmod/fchown last so that no one else could access the /* We do fchmod/fchown last so that no one else could access the
* partially created directory and change content we're laying out. * partially created directory and change content we're laying out.
*/ */
if (!did_exist) if (!did_exist)
{ {
if (TEMP_FAILURE_RETRY (fchmod (destination_dfd, g_file_info_get_attribute_uint32 (source_info, "unix::mode"))) < 0) if (TEMP_FAILURE_RETRY (fchmod (destination_dfd, mode)) < 0)
return glnx_throw_errno (error); return glnx_throw_errno (error);
} }
if (!did_exist && options->mode != OSTREE_REPO_CHECKOUT_MODE_USER) if (!did_exist && options->mode != OSTREE_REPO_CHECKOUT_MODE_USER)
{ {
if (TEMP_FAILURE_RETRY (fchown (destination_dfd, if (TEMP_FAILURE_RETRY (fchown (destination_dfd, uid, gid)) < 0)
g_file_info_get_attribute_uint32 (source_info, "unix::uid"),
g_file_info_get_attribute_uint32 (source_info, "unix::gid"))) < 0)
return glnx_throw_errno (error); return glnx_throw_errno (error);
} }
@ -783,8 +805,7 @@ checkout_tree_at (OstreeRepo *self,
/* Special case handling for subpath of a non-directory */ /* Special case handling for subpath of a non-directory */
if (g_file_info_get_file_type (source_info) != G_FILE_TYPE_DIRECTORY) if (g_file_info_get_file_type (source_info) != G_FILE_TYPE_DIRECTORY)
return checkout_one_file_at (self, options, &state, return checkout_one_file_at (self, options, &state,
(GFile *) source, ostree_repo_file_get_checksum (source),
source_info,
destination_parent_fd, destination_parent_fd,
g_file_info_get_name (source_info), g_file_info_get_name (source_info),
cancellable, error); cancellable, error);
@ -794,9 +815,13 @@ checkout_tree_at (OstreeRepo *self,
*/ */
g_auto(OstreeRepoMemoryCacheRef) memcache_ref; g_auto(OstreeRepoMemoryCacheRef) memcache_ref;
_ostree_repo_memory_cache_ref_init (&memcache_ref, self); _ostree_repo_memory_cache_ref_init (&memcache_ref, self);
g_assert_cmpint (g_file_info_get_file_type (source_info), ==, G_FILE_TYPE_DIRECTORY);
const char *dirtree_checksum = ostree_repo_file_tree_get_contents_checksum (source);
const char *dirmeta_checksum = ostree_repo_file_tree_get_metadata_checksum (source);
return checkout_tree_at_recurse (self, options, &state, destination_parent_fd, return checkout_tree_at_recurse (self, options, &state, destination_parent_fd,
destination_name, destination_name,
source, source_info, dirtree_checksum, dirmeta_checksum,
cancellable, error); cancellable, error);
} }