diff options
author | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2021-07-13 11:41:02 +0200 |
---|---|---|
committer | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2021-07-15 17:14:26 +0200 |
commit | 022c4795510306e08a4aba31504ca382d41c7fac (patch) | |
tree | d4c29f62038d0f336d90f32f46bc5b4f5c3ddc28 | |
parent | f27e542442d19436f1428cc22bb03aca398d37a7 (diff) |
hugofs: Make FileMeta a struct
This commit started out investigating a `concurrent map read write` issue, ending by replacing the map with a struct.
This is easier to reason about, and it's more effective:
```
name old time/op new time/op delta
SiteNew/Regular_Deep_content_tree-16 71.5ms ± 3% 69.4ms ± 5% ~ (p=0.200 n=4+4)
name old alloc/op new alloc/op delta
SiteNew/Regular_Deep_content_tree-16 29.7MB ± 0% 27.9MB ± 0% -5.82% (p=0.029 n=4+4)
name old allocs/op new allocs/op delta
SiteNew/Regular_Deep_content_tree-16 313k ± 0% 303k ± 0% -3.35% (p=0.029 n=4+4)
```
See #8749
44 files changed, 423 insertions, 440 deletions
diff --git a/commands/hugo.go b/commands/hugo.go index 0f6e4274a..2b53d5cd6 100644 --- a/commands/hugo.go +++ b/commands/hugo.go @@ -697,7 +697,7 @@ func (c *commandeer) getDirList() ([]string, error) { return filepath.SkipDir } - filenames = append(filenames, fi.Meta().Filename()) + filenames = append(filenames, fi.Meta().Filename) } return nil @@ -706,7 +706,7 @@ func (c *commandeer) getDirList() ([]string, error) { watchFiles := c.hugo().PathSpec.BaseFs.WatchDirs() for _, fi := range watchFiles { if !fi.IsDir() { - filenames = append(filenames, fi.Meta().Filename()) + filenames = append(filenames, fi.Meta().Filename) continue } diff --git a/commands/new.go b/commands/new.go index a2d873717..44e2a7e40 100644 --- a/commands/new.go +++ b/commands/new.go @@ -118,7 +118,7 @@ func newContentPathSection(h *hugolib.HugoSites, path string) (string, string) { if h != nil { for _, dir := range h.BaseFs.Content.Dirs { - createpath = strings.TrimPrefix(createpath, dir.Meta().Filename()) + createpath = strings.TrimPrefix(createpath, dir.Meta().Filename) } } diff --git a/common/hugo/hugo.go b/common/hugo/hugo.go index bf3dee26e..4548de93a 100644 --- a/common/hugo/hugo.go +++ b/common/hugo/hugo.go @@ -102,7 +102,7 @@ func GetExecEnviron(workDir string, cfg config.Provider, fs afero.Fs) []string { if err == nil { for _, fi := range fis { key := fmt.Sprintf("HUGO_FILE_%s", strings.ReplaceAll(strings.ToUpper(fi.Name()), ".", "_")) - value := fi.(hugofs.FileMetaInfo).Meta().Filename() + value := fi.(hugofs.FileMetaInfo).Meta().Filename config.SetEnvVars(&env, key, value) } } diff --git a/create/content.go b/create/content.go index 797ffe758..ea065423e 100644 --- a/create/content.go +++ b/create/content.go @@ -123,7 +123,7 @@ func NewContent( func targetSite(sites *hugolib.HugoSites, fi hugofs.FileMetaInfo) *hugolib.Site { for _, s := range sites.Sites { - if fi.Meta().Lang() == s.Language().Lang { + if fi.Meta().Lang == s.Language().Lang { return s } } @@ -137,7 +137,7 @@ func newContentFromDir( cm archetypeMap, name, targetPath string) error { for _, f := range cm.otherFiles { meta := f.Meta() - filename := meta.Path() + filename := meta.Path // Just copy the file to destination. in, err := meta.Open() if err != nil { @@ -166,7 +166,7 @@ func newContentFromDir( } for _, f := range cm.contentFiles { - filename := f.Meta().Path() + filename := f.Meta().Path s := targetSite(sites, f) targetFilename := filepath.Join(targetPath, strings.TrimPrefix(filename, archetypeDir)) @@ -274,7 +274,7 @@ func resolveContentPath(sites *hugolib.HugoSites, fs afero.Fs, targetPath string for _, dir := range sites.BaseFs.Content.Dirs { meta := dir.Meta() - contentDir := meta.Filename() + contentDir := meta.Filename if !strings.HasSuffix(contentDir, helpers.FilePathSeparator) { contentDir += helpers.FilePathSeparator @@ -282,7 +282,7 @@ func resolveContentPath(sites *hugolib.HugoSites, fs afero.Fs, targetPath string if strings.HasPrefix(targetPath, contentDir) { siteContentDir = contentDir - dirLang = meta.Lang() + dirLang = meta.Lang break } } @@ -317,8 +317,8 @@ func resolveContentPath(sites *hugolib.HugoSites, fs afero.Fs, targetPath string } else { var contentDir string for _, dir := range sites.BaseFs.Content.Dirs { - contentDir = dir.Meta().Filename() - if dir.Meta().Lang() == s.Lang() { + contentDir = dir.Meta().Filename + if dir.Meta().Lang == s.Lang() { break } } diff --git a/helpers/general.go b/helpers/general.go index c5af83b66..25decbbc1 100644 --- a/helpers/general.go +++ b/helpers/general.go @@ -497,7 +497,7 @@ func PrintFs(fs afero.Fs, path string, w io.Writer) { var filename string var meta interface{} if fim, ok := info.(hugofs.FileMetaInfo); ok { - filename = fim.Meta().Filename() + filename = fim.Meta().Filename meta = fim.Meta() } fmt.Fprintf(w, " %q %q\t\t%v\n", path, filename, meta) diff --git a/hugofs/decorators.go b/hugofs/decorators.go index 62d234944..364a3e23e 100644 --- a/hugofs/decorators.go +++ b/hugofs/decorators.go @@ -23,7 +23,7 @@ import ( "github.com/spf13/afero" ) -func decorateDirs(fs afero.Fs, meta FileMeta) afero.Fs { +func decorateDirs(fs afero.Fs, meta *FileMeta) afero.Fs { ffs := &baseFileDecoratorFs{Fs: fs} decorator := func(fi os.FileInfo, name string) (os.FileInfo, error) { @@ -82,9 +82,11 @@ func NewBaseFileDecorator(fs afero.Fs, callbacks ...func(fi FileMetaInfo)) afero decorator := func(fi os.FileInfo, filename string) (os.FileInfo, error) { // Store away the original in case it's a symlink. - meta := FileMeta{metaKeyName: fi.Name()} + meta := NewFileMeta() + meta.Name = fi.Name() + if fi.IsDir() { - meta[metaKeyJoinStat] = func(name string) (FileMetaInfo, error) { + meta.JoinStatFunc = func(name string) (FileMetaInfo, error) { joinedFilename := filepath.Join(filename, name) fi, _, err := lstatIfPossible(fs, joinedFilename) if err != nil { @@ -102,7 +104,7 @@ func NewBaseFileDecorator(fs afero.Fs, callbacks ...func(fi FileMetaInfo)) afero isSymlink := isSymlink(fi) if isSymlink { - meta[metaKeyOriginalFilename] = filename + meta.OriginalFilename = filename var link string var err error link, fi, err = evalSymlinks(fs, filename) @@ -110,7 +112,7 @@ func NewBaseFileDecorator(fs afero.Fs, callbacks ...func(fi FileMetaInfo)) afero return nil, err } filename = link - meta[metaKeyIsSymlink] = true + meta.IsSymlink = true } opener := func() (afero.File, error) { diff --git a/hugofs/fileinfo.go b/hugofs/fileinfo.go index 4f318205a..fcf35d956 100644 --- a/hugofs/fileinfo.go +++ b/hugofs/fileinfo.go @@ -17,6 +17,7 @@ package hugofs import ( "os" "path/filepath" + "reflect" "runtime" "sort" "strings" @@ -27,242 +28,128 @@ import ( "github.com/pkg/errors" - "github.com/spf13/cast" - "github.com/gohugoio/hugo/common/hreflect" "github.com/spf13/afero" ) -const ( - metaKeyFilename = "filename" - - metaKeySourceRoot = "sourceRoot" - metaKeyBaseDir = "baseDir" // Abs base directory of source file. - metaKeyMountRoot = "mountRoot" - metaKeyModule = "module" - metaKeyOriginalFilename = "originalFilename" - metaKeyName = "name" - metaKeyPath = "path" - metaKeyPathWalk = "pathWalk" - metaKeyLang = "lang" - metaKeyWeight = "weight" - metaKeyOrdinal = "ordinal" - metaKeyFs = "fs" - metaKeyOpener = "opener" - metaKeyIsOrdered = "isOrdered" - metaKeyIsSymlink = "isSymlink" - metaKeyJoinStat = "joinStat" - metaKeySkipDir = "skipDir" - metaKeyClassifier = "classifier" - metaKeyTranslationBaseName = "translationBaseName" - metaKeyTranslationBaseNameWithExt = "translationBaseNameWithExt" - metaKeyTranslations = "translations" - metaKeyDecoraterPath = "decoratorPath" -) - -type FileMeta map[string]interface{} - -func (f FileMeta) GetInt(key string) int { - return cast.ToInt(f[key]) -} - -func (f FileMeta) GetString(key string) string { - return cast.ToString(f[key]) -} - -func (f FileMeta) GetBool(key string) bool { - return cast.ToBool(f[key]) -} - -func (f FileMeta) Filename() string { - return f.stringV(metaKeyFilename) -} - -func (f FileMeta) OriginalFilename() string { - return f.stringV(metaKeyOriginalFilename) -} - -func (f FileMeta) SkipDir() bool { - return f.GetBool(metaKeySkipDir) -} - -func (f FileMeta) TranslationBaseName() string { - return f.stringV(metaKeyTranslationBaseName) -} - -func (f FileMeta) TranslationBaseNameWithExt() string { - return f.stringV(metaKeyTranslationBaseNameWithExt) -} - -func (f FileMeta) Translations() []string { - return cast.ToStringSlice(f[metaKeyTranslations]) -} - -func (f FileMeta) Name() string { - return f.stringV(metaKeyName) -} - -func (f FileMeta) Classifier() files.ContentClass { - c, found := f[metaKeyClassifier] - if found { - return c.(files.ContentClass) - } - - return files.ContentClassFile // For sorting -} - -func (f FileMeta) Lang() string { - return f.stringV(metaKeyLang) -} - -// Path returns the relative file path to where this file is mounted. -func (f FileMeta) Path() string { - return f.stringV(metaKeyPath) +func NewFileMeta() *FileMeta { + return &FileMeta{} } // PathFile returns the relative file path for the file source. -func (f FileMeta) PathFile() string { - base := f.stringV(metaKeyBaseDir) - if base == "" { +func (f *FileMeta) PathFile() string { + if f.BaseDir == "" { return "" } - return strings.TrimPrefix(strings.TrimPrefix(f.Filename(), base), filepathSeparator) + return strings.TrimPrefix(strings.TrimPrefix(f.Filename, f.BaseDir), filepathSeparator) } -func (f FileMeta) SourceRoot() string { - return f.stringV(metaKeySourceRoot) -} +type FileMeta struct { + Name string + Filename string + Path string + PathWalk string + OriginalFilename string + BaseDir string -func (f FileMeta) MountRoot() string { - return f.stringV(metaKeyMountRoot) -} + SourceRoot string + MountRoot string + Module string -func (f FileMeta) Module() string { - return f.stringV(metaKeyModule) -} + Weight int + Ordinal int + IsOrdered bool + IsSymlink bool + IsRootFile bool + Watch bool -func (f FileMeta) Weight() int { - return f.GetInt(metaKeyWeight) -} + Classifier files.ContentClass -func (f FileMeta) Ordinal() int { - return f.GetInt(metaKeyOrdinal) -} + SkipDir bool -func (f FileMeta) IsOrdered() bool { - return f.GetBool(metaKeyIsOrdered) -} + Lang string + TranslationBaseName string + TranslationBaseNameWithExt string + Translations []string -// IsSymlink returns whether this comes from a symlinked file or directory. -func (f FileMeta) IsSymlink() bool { - return f.GetBool(metaKeyIsSymlink) + Fs afero.Fs + OpenFunc func() (afero.File, error) + JoinStatFunc func(name string) (FileMetaInfo, error) } -func (f FileMeta) Watch() bool { - if v, found := f["watch"]; found { - return v.(bool) +func (m *FileMeta) Copy() *FileMeta { + if m == nil { + return NewFileMeta() } - return false + c := *m + return &c } -func (f FileMeta) Fs() afero.Fs { - if v, found := f[metaKeyFs]; found { - return v.(afero.Fs) +func (m *FileMeta) Merge(from *FileMeta) { + if m == nil || from == nil { + return } - return nil -} + dstv := reflect.Indirect(reflect.ValueOf(m)) + srcv := reflect.Indirect(reflect.ValueOf(from)) -func (f FileMeta) GetOpener() func() (afero.File, error) { - o, found := f[metaKeyOpener] - if !found { - return nil + for i := 0; i < dstv.NumField(); i++ { + v := dstv.Field(i) + if !hreflect.IsTruthfulValue(v) { + v.Set(srcv.Field(i)) + } } - return o.(func() (afero.File, error)) } -func (f FileMeta) Open() (afero.File, error) { - v, found := f[metaKeyOpener] - if !found { - return nil, errors.New("file opener not found") +func (f *FileMeta) Open() (afero.File, error) { + if f.OpenFunc == nil { + return nil, errors.New("OpenFunc not set") } - return v.(func() (afero.File, error))() + return f.OpenFunc() } -func (f FileMeta) JoinStat(name string) (FileMetaInfo, error) { - v, found := f[metaKeyJoinStat] - if !found { +func (f *FileMeta) JoinStat(name string) (FileMetaInfo, error) { + if f.JoinStatFunc == nil { return nil, os.ErrNotExist } - return v.(func(name string) (FileMetaInfo, error))(name) -} - -func (f FileMeta) stringV(key string) string { - if v, found := f[key]; found { - return v.(string) - } - return "" -} - -func (f FileMeta) setIfNotZero(key string, val interface{}) { - if !hreflect.IsTruthful(val) { - return - } - f[key] = val + return f.JoinStatFunc(name) } type FileMetaInfo interface { os.FileInfo - Meta() FileMeta + Meta() *FileMeta } type fileInfoMeta struct { os.FileInfo - m FileMeta + m *FileMeta } // Name returns the file's name. Note that we follow symlinks, // if supported by the file system, and the Name given here will be the // name of the symlink, which is what Hugo needs in all situations. func (fi *fileInfoMeta) Name() string { - if name := fi.m.Name(); name != "" { + if name := fi.m.Name; name != "" { return name } return fi.FileInfo.Name() } -func (fi *fileInfoMeta) Meta() FileMeta { +func (fi *fileInfoMeta) Meta() *FileMeta { return fi.m } -func NewFileMetaInfo(fi os.FileInfo, m FileMeta) FileMetaInfo { +func NewFileMetaInfo(fi os.FileInfo, m *FileMeta) FileMetaInfo { + if m == nil { + panic("FileMeta must be set") + } if fim, ok := fi.(FileMetaInfo); ok { - mergeFileMeta(fim.Meta(), m) + m.Merge(fim.Meta()) } return &fileInfoMeta{FileInfo: fi, m: m} } -func copyFileMeta(m FileMeta) FileMeta { - c := make(FileMeta) - for k, v := range m { - c[k] = v - } - return c -} - -// Merge metadata, last entry wins. -func mergeFileMeta(from, to FileMeta) { - if from == nil { - return - } - for k, v := range from { - if _, found := to[k]; !found { - to[k] = v - } - } -} - type dirNameOnlyFileInfo struct { name string modTime time.Time @@ -292,16 +179,16 @@ func (fi *dirNameOnlyFileInfo) Sys() interface{} { return nil } -func newDirNameOnlyFileInfo(name string, meta FileMeta, fileOpener func() (afero.File, error)) FileMetaInfo { +func newDirNameOnlyFileInfo(name string, meta *FileMeta, fileOpener func() (afero.File, error)) FileMetaInfo { name = normalizeFilename(name) _, base := filepath.Split(name) - m := copyFileMeta(meta) - if _, found := m[metaKeyFilename]; !found { - m.setIfNotZero(metaKeyFilename, name) + m := meta.Copy() + if m.Filename == "" { + m.Filename = name } - m[metaKeyOpener] = fileOpener - m[metaKeyIsOrdered] = false + m.OpenFunc = fileOpener + m.IsOrdered = false return NewFileMetaInfo( &dirNameOnlyFileInfo{name: base, modTime: time.Now()}, @@ -312,8 +199,8 @@ func newDirNameOnlyFileInfo(name string, meta FileMeta, fileOpener func() (afero func decorateFileInfo( fi os.FileInfo, fs afero.Fs, opener func() (afero.File, error), - filename, filepath string, inMeta FileMeta) FileMetaInfo { - var meta FileMeta + filename, filepath string, inMeta *FileMeta) FileMetaInfo { + var meta *FileMeta var fim FileMetaInfo filepath = strings.TrimPrefix(filepath, filepathSeparator) @@ -322,16 +209,26 @@ func decorateFileInfo( if fim, ok = fi.(FileMetaInfo); ok { meta = fim.Meta() } else { - meta = make(FileMeta) + meta = NewFileMeta() fim = NewFileMetaInfo(fi, meta) } - meta.setIfNotZero(metaKeyOpener, opener) - meta.setIfNotZero(metaKeyFs, fs) - meta.setIfNotZero(metaKeyPath, normalizeFilename(filepath)) - meta.setIfNotZero(metaKeyFilename, normalizeFilename(filename)) + if opener != nil { + meta.OpenFunc = opener + } + if fs != nil { + meta.Fs = fs + } + nfilepath := normalizeFilename(filepath) + nfilename := normalizeFilename(filename) + if nfilepath != "" { + meta.Path = nfilepath + } + if nfilename != "" { + meta.Filename = nfilename + } - mergeFileMeta(inMeta, meta) + meta.Merge(inMeta) return fim } @@ -377,6 +274,6 @@ func fromSlash(filenames []string) []string { func sortFileInfos(fis []os.FileInfo) { sort.Slice(fis, func(i, j int) bool { fimi, fimj := fis[i].(FileMetaInfo), fis[j].(FileMetaInfo) - return fimi.Meta().Filename() < fimj.Meta().Filename() + return fimi.Meta().Filename < fimj.Meta().Filename }) } diff --git a/hugofs/fileinfo_test.go b/hugofs/fileinfo_test.go new file mode 100644 index 000000000..8d6a2ff7a --- /dev/null +++ b/hugofs/fileinfo_test.go @@ -0,0 +1,51 @@ +// Copyright 2021 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 |