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:
parent
7896bcbe65
commit
e6f17b949d
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue