Compare commits

...

5 Commits

Author SHA1 Message Date
Seednode 7546c6257f Add garbage collection after every index rebuild, to test something 2024-01-14 10:12:24 -06:00
Seednode ee09812376 Remove unused CompressionFormats variable 2024-01-14 10:00:34 -06:00
Seednode 6bd97f30c2 Re-use zstd encoder 2024-01-14 09:43:22 -06:00
Seednode 2f06ae3605 Remove non-zstd compression options 2024-01-14 09:29:22 -06:00
Seednode 7868f0d7ad Register all pprof endpoints 2024-01-14 09:06:22 -06:00
7 changed files with 110 additions and 159 deletions

View File

@ -25,10 +25,15 @@ You can restrict access to certain functionality by prepending a secret string t
For example, providing the `--admin-prefix=abc123` flag will register the index rebuild path as `/abc123/index/rebuild`. For example, providing the `--admin-prefix=abc123` flag will register the index rebuild path as `/abc123/index/rebuild`.
The restricted paths are: The restricted paths are:
- `/debug/pprof/` - `/debug/pprof/allocs`
- `/debug/pprof/block`
- `/debug/pprof/cmdline` - `/debug/pprof/cmdline`
- `/debug/pprof/goroutine`
- `/debug/pprof/heap`
- `/debug/pprof/mutex`
- `/debug/pprof/profile` - `/debug/pprof/profile`
- `/debug/pprof/symbol` - `/debug/pprof/symbol`
- `/debug/pprof/threadcreate`
- `/debug/pprof/trace` - `/debug/pprof/trace`
- `/extensions/available` - `/extensions/available`
- `/extensions/enabled` - `/extensions/enabled`
@ -153,8 +158,6 @@ Flags:
--case-sensitive use case-sensitive matching for filters --case-sensitive use case-sensitive matching for filters
--code enable support for source code files --code enable support for source code files
--code-theme string theme for source code syntax highlighting (default "solarized-dark256") --code-theme string theme for source code syntax highlighting (default "solarized-dark256")
--compression string compression format to use for index (none, zlib, zstd) (default "zstd")
--compression-fast use fastest compression level (default is best)
--concurrency int maximum concurrency for scan threads (default 2147483647) --concurrency int maximum concurrency for scan threads (default 2147483647)
-d, --debug display even more verbose logs -d, --debug display even more verbose logs
--disable-buttons disable first/prev/next/last buttons --disable-buttons disable first/prev/next/last buttons

View File

@ -16,7 +16,6 @@ import (
var ( var (
ErrInvalidAdminPrefix = errors.New("admin path must match the pattern " + AllowedCharacters) ErrInvalidAdminPrefix = errors.New("admin path must match the pattern " + AllowedCharacters)
ErrInvalidCompression = errors.New("supported compression formats: none, zlib, zstd")
ErrInvalidConcurrency = errors.New("concurrency limit must be a positive integer") ErrInvalidConcurrency = errors.New("concurrency limit must be a positive integer")
ErrInvalidFileCountRange = errors.New("maximum file count limit must be greater than or equal to minimum file count limit") ErrInvalidFileCountRange = errors.New("maximum file count limit must be greater than or equal to minimum file count limit")
ErrInvalidFileCountValue = errors.New("file count limits must be non-negative integers no greater than 2147483647") ErrInvalidFileCountValue = errors.New("file count limits must be non-negative integers no greater than 2147483647")

View File

@ -21,6 +21,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/klauspost/compress/zstd"
"seedno.de/seednode/roulette/types" "seedno.de/seednode/roulette/types"
) )
@ -447,18 +448,18 @@ func scanPaths(paths []string, sort string, index *fileIndex, formats types.Type
return list return list
} }
func fileList(paths []string, filters *filters, sort string, index *fileIndex, formats types.Types, errorChannel chan<- error) []string { func fileList(paths []string, filters *filters, sort string, index *fileIndex, formats types.Types, encoder *zstd.Encoder, errorChannel chan<- error) []string {
switch { switch {
case Index && !index.isEmpty() && filters.isEmpty(): case Index && !index.isEmpty() && filters.isEmpty():
return index.List() return index.List()
case Index && !index.isEmpty() && !filters.isEmpty(): case Index && !index.isEmpty() && !filters.isEmpty():
return filters.apply(index.List()) return filters.apply(index.List())
case Index && index.isEmpty() && !filters.isEmpty(): case Index && index.isEmpty() && !filters.isEmpty():
index.set(scanPaths(paths, sort, index, formats, errorChannel)) index.set(scanPaths(paths, sort, index, formats, errorChannel), encoder, errorChannel)
return filters.apply(index.List()) return filters.apply(index.List())
case Index && index.isEmpty() && filters.isEmpty(): case Index && index.isEmpty() && filters.isEmpty():
index.set(scanPaths(paths, sort, index, formats, errorChannel)) index.set(scanPaths(paths, sort, index, formats, errorChannel), encoder, errorChannel)
return index.List() return index.List()
case !Index && !filters.isEmpty(): case !Index && !filters.isEmpty():

View File

@ -5,12 +5,11 @@ Copyright © 2023 Seednode <seednode@seedno.de>
package cmd package cmd
import ( import (
"compress/zlib"
"encoding/gob" "encoding/gob"
"fmt" "fmt"
"io"
"net/http" "net/http"
"os" "os"
"runtime"
"sync" "sync"
"time" "time"
@ -19,12 +18,6 @@ import (
"seedno.de/seednode/roulette/types" "seedno.de/seednode/roulette/types"
) )
var CompressionFormats = []string{
"none",
"zlib",
"zstd",
}
type fileIndex struct { type fileIndex struct {
mutex *sync.RWMutex mutex *sync.RWMutex
list []string list []string
@ -63,7 +56,7 @@ func (index *fileIndex) remove(path string) {
index.mutex.Unlock() index.mutex.Unlock()
} }
func (index *fileIndex) set(val []string) { func (index *fileIndex) set(val []string, encoder *zstd.Encoder, errorChannel chan<- error) {
length := len(val) length := len(val)
if length < 1 { if length < 1 {
@ -76,7 +69,7 @@ func (index *fileIndex) set(val []string) {
index.mutex.Unlock() index.mutex.Unlock()
if Index && IndexFile != "" { if Index && IndexFile != "" {
index.Export(IndexFile) index.Export(IndexFile, encoder, errorChannel)
} }
} }
@ -94,54 +87,19 @@ func (index *fileIndex) isEmpty() bool {
return length == 0 return length == 0
} }
func getReader(format string, file io.Reader) (io.ReadCloser, error) { func (index *fileIndex) Export(path string, encoder *zstd.Encoder, errorChannel chan<- error) {
switch format {
case "none":
return io.NopCloser(file), nil
case "zlib":
return zlib.NewReader(file)
case "zstd":
reader, err := zstd.NewReader(file)
if err != nil {
return io.NopCloser(file), err
}
return reader.IOReadCloser(), nil
}
return io.NopCloser(file), ErrInvalidCompression
}
func getWriter(format string, file io.WriteCloser) (io.WriteCloser, error) {
switch {
case format == "none":
return file, nil
case format == "zlib" && CompressionFast:
return zlib.NewWriterLevel(file, zlib.BestSpeed)
case format == "zlib":
return zlib.NewWriterLevel(file, zlib.BestCompression)
case format == "zstd" && CompressionFast:
return zstd.NewWriter(file, zstd.WithEncoderLevel(zstd.SpeedFastest))
case format == "zstd":
return zstd.NewWriter(file, zstd.WithEncoderLevel(zstd.SpeedBestCompression))
}
return file, ErrInvalidCompression
}
func (index *fileIndex) Export(path string) error {
startTime := time.Now() startTime := time.Now()
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil { if err != nil {
return err errorChannel <- err
return
} }
defer file.Close() defer file.Close()
encoder, err := getWriter(Compression, file) encoder.Reset(file)
if err != nil {
return err
}
defer encoder.Close() defer encoder.Close()
enc := gob.NewEncoder(encoder) enc := gob.NewEncoder(encoder)
@ -151,22 +109,22 @@ func (index *fileIndex) Export(path string) error {
if err != nil { if err != nil {
index.mutex.RUnlock() index.mutex.RUnlock()
return err errorChannel <- err
return
} }
length := len(index.list) length := len(index.list)
index.mutex.RUnlock() index.mutex.RUnlock()
// Close encoder prior to checking file size, // Close encoder prior to checking file size,
// to ensure the correct value is returned. // to ensure the correct value is returned.
// If no compression is used, skip this step,
// as the encoder is just the file itself.
if Compression != "none" {
encoder.Close() encoder.Close()
}
stats, err := file.Stat() stats, err := file.Stat()
if err != nil { if err != nil {
return err errorChannel <- err
return
} }
if Verbose { if Verbose {
@ -178,27 +136,31 @@ func (index *fileIndex) Export(path string) error {
time.Since(startTime).Round(time.Microsecond), time.Since(startTime).Round(time.Microsecond),
) )
} }
return nil
} }
func (index *fileIndex) Import(path string) error { func (index *fileIndex) Import(path string, errorChannel chan<- error) {
startTime := time.Now() startTime := time.Now()
file, err := os.OpenFile(path, os.O_RDONLY, 0600) file, err := os.OpenFile(path, os.O_RDONLY, 0600)
if err != nil { if err != nil {
return err errorChannel <- err
return
} }
defer file.Close() defer file.Close()
stats, err := file.Stat() stats, err := file.Stat()
if err != nil { if err != nil {
return err errorChannel <- err
return
} }
reader, err := getReader(Compression, file) reader, err := zstd.NewReader(file)
if err != nil { if err != nil {
return err errorChannel <- err
return
} }
defer reader.Close() defer reader.Close()
@ -209,7 +171,9 @@ func (index *fileIndex) Import(path string) error {
if err != nil { if err != nil {
index.mutex.Unlock() index.mutex.Unlock()
return err errorChannel <- err
return
} }
length := len(index.list) length := len(index.list)
index.mutex.Unlock() index.mutex.Unlock()
@ -223,17 +187,15 @@ func (index *fileIndex) Import(path string) error {
time.Since(startTime).Round(time.Microsecond), time.Since(startTime).Round(time.Microsecond),
) )
} }
return nil
} }
func serveIndexRebuild(args []string, index *fileIndex, formats types.Types, errorChannel chan<- error) httprouter.Handle { func serveIndexRebuild(args []string, index *fileIndex, formats types.Types, encoder *zstd.Encoder, errorChannel chan<- error) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
startTime := time.Now() startTime := time.Now()
index.clear() index.clear()
fileList(args, &filters{}, "", index, formats, errorChannel) fileList(args, &filters{}, "", index, formats, encoder, errorChannel)
w.Header().Set("Content-Type", "text/plain") w.Header().Set("Content-Type", "text/plain")
@ -251,16 +213,15 @@ func serveIndexRebuild(args []string, index *fileIndex, formats types.Types, err
time.Since(startTime).Round(time.Microsecond), time.Since(startTime).Round(time.Microsecond),
) )
} }
runtime.GC()
} }
} }
func importIndex(args []string, index *fileIndex, formats types.Types, errorChannel chan<- error) { func importIndex(args []string, index *fileIndex, formats types.Types, encoder *zstd.Encoder, errorChannel chan<- error) {
if IndexFile != "" { if IndexFile != "" {
err := index.Import(IndexFile) index.Import(IndexFile, errorChannel)
if err == nil {
return
}
} }
fileList(args, &filters{}, "", index, formats, errorChannel) fileList(args, &filters{}, "", index, formats, encoder, errorChannel)
} }

View File

@ -5,34 +5,20 @@ Copyright © 2023 Seednode <seednode@seedno.de>
package cmd package cmd
import ( import (
"fmt"
"net/http"
"net/http/pprof" "net/http/pprof"
"strings"
"time"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
) )
func registerProfileHandler(mux *httprouter.Router, verb, path string, handler http.HandlerFunc) {
mux.HandlerFunc(verb, path, handler)
if Redact && AdminPrefix != "" {
path = strings.ReplaceAll(path, AdminPrefix, "/<admin_prefix>")
}
if Handlers {
fmt.Printf("%s | SERVE: Registered handler for %s\n",
time.Now().Format(logDate),
path,
)
}
}
func registerProfileHandlers(mux *httprouter.Router) { func registerProfileHandlers(mux *httprouter.Router) {
registerProfileHandler(mux, "GET", Prefix+AdminPrefix+"/debug/pprof/", pprof.Index) mux.Handler("GET", Prefix+AdminPrefix+"/debug/pprof/allocs", pprof.Handler("allocs"))
registerProfileHandler(mux, "GET", Prefix+AdminPrefix+"/debug/pprof/cmdline", pprof.Cmdline) mux.Handler("GET", Prefix+AdminPrefix+"/debug/pprof/block", pprof.Handler("block"))
registerProfileHandler(mux, "GET", Prefix+AdminPrefix+"/debug/pprof/profile", pprof.Profile) mux.Handler("GET", Prefix+AdminPrefix+"/debug/pprof/goroutine", pprof.Handler("goroutine"))
registerProfileHandler(mux, "GET", Prefix+AdminPrefix+"/debug/pprof/symbol", pprof.Symbol) mux.Handler("GET", Prefix+AdminPrefix+"/debug/pprof/heap", pprof.Handler("heap"))
registerProfileHandler(mux, "GET", Prefix+AdminPrefix+"/debug/pprof/trace", pprof.Trace) mux.Handler("GET", Prefix+AdminPrefix+"/debug/pprof/mutex", pprof.Handler("mutex"))
mux.Handler("GET", Prefix+AdminPrefix+"/debug/pprof/threadcreate", pprof.Handler("threadcreate"))
mux.HandlerFunc("GET", Prefix+AdminPrefix+"/debug/pprof/cmdline", pprof.Cmdline)
mux.HandlerFunc("GET", Prefix+AdminPrefix+"/debug/pprof/profile", pprof.Profile)
mux.HandlerFunc("GET", Prefix+AdminPrefix+"/debug/pprof/symbol", pprof.Symbol)
mux.HandlerFunc("GET", Prefix+AdminPrefix+"/debug/pprof/trace", pprof.Trace)
} }

View File

@ -10,7 +10,6 @@ import (
"math" "math"
"os" "os"
"regexp" "regexp"
"slices"
"time" "time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -18,7 +17,7 @@ import (
const ( const (
AllowedCharacters string = `^[A-z0-9.\-_]+$` AllowedCharacters string = `^[A-z0-9.\-_]+$`
ReleaseVersion string = "5.2.0" ReleaseVersion string = "5.4.3"
) )
var ( var (
@ -31,8 +30,6 @@ var (
CaseSensitive bool CaseSensitive bool
Code bool Code bool
CodeTheme string CodeTheme string
Compression string
CompressionFast bool
Concurrency int Concurrency int
Debug bool Debug bool
DisableButtons bool DisableButtons bool
@ -91,8 +88,6 @@ var (
return ErrInvalidConcurrency return ErrInvalidConcurrency
case Ignore && !regexp.MustCompile(AllowedCharacters).MatchString(IgnoreFile): case Ignore && !regexp.MustCompile(AllowedCharacters).MatchString(IgnoreFile):
return ErrInvalidIgnoreFile return ErrInvalidIgnoreFile
case !slices.Contains(CompressionFormats, Compression):
return ErrInvalidCompression
case AdminPrefix != "" && !regexp.MustCompile(AllowedCharacters).MatchString(AdminPrefix): case AdminPrefix != "" && !regexp.MustCompile(AllowedCharacters).MatchString(AdminPrefix):
return ErrInvalidAdminPrefix return ErrInvalidAdminPrefix
case AdminPrefix != "": case AdminPrefix != "":
@ -131,8 +126,6 @@ func init() {
rootCmd.Flags().BoolVar(&CaseSensitive, "case-sensitive", false, "use case-sensitive matching for filters") rootCmd.Flags().BoolVar(&CaseSensitive, "case-sensitive", false, "use case-sensitive matching for filters")
rootCmd.Flags().BoolVar(&Code, "code", false, "enable support for source code files") rootCmd.Flags().BoolVar(&Code, "code", false, "enable support for source code files")
rootCmd.Flags().StringVar(&CodeTheme, "code-theme", "solarized-dark256", "theme for source code syntax highlighting") rootCmd.Flags().StringVar(&CodeTheme, "code-theme", "solarized-dark256", "theme for source code syntax highlighting")
rootCmd.Flags().StringVar(&Compression, "compression", "zstd", "compression format to use for index (none, zlib, zstd)")
rootCmd.Flags().BoolVar(&CompressionFast, "compression-fast", false, "use fastest compression level (default is best)")
rootCmd.Flags().IntVar(&Concurrency, "concurrency", 10240, "maximum concurrency for scan threads") rootCmd.Flags().IntVar(&Concurrency, "concurrency", 10240, "maximum concurrency for scan threads")
rootCmd.Flags().BoolVarP(&Debug, "debug", "d", false, "display even more verbose logs") rootCmd.Flags().BoolVarP(&Debug, "debug", "d", false, "display even more verbose logs")
rootCmd.Flags().BoolVar(&DisableButtons, "disable-buttons", false, "disable first/prev/next/last buttons") rootCmd.Flags().BoolVar(&DisableButtons, "disable-buttons", false, "disable first/prev/next/last buttons")

View File

@ -21,6 +21,7 @@ import (
"time" "time"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"github.com/klauspost/compress/zstd"
"github.com/yosssi/gohtml" "github.com/yosssi/gohtml"
"seedno.de/seednode/roulette/types" "seedno.de/seednode/roulette/types"
"seedno.de/seednode/roulette/types/audio" "seedno.de/seednode/roulette/types/audio"
@ -31,6 +32,8 @@ import (
"seedno.de/seednode/roulette/types/video" "seedno.de/seednode/roulette/types/video"
) )
var ()
const ( const (
logDate string = `2006-01-02T15:04:05.000-07:00` logDate string = `2006-01-02T15:04:05.000-07:00`
sourcePrefix string = `/source` sourcePrefix string = `/source`
@ -168,7 +171,7 @@ func serveStaticFile(paths []string, index *fileIndex, errorChannel chan<- error
} }
} }
func serveRoot(paths []string, index *fileIndex, formats types.Types, errorChannel chan<- error) httprouter.Handle { func serveRoot(paths []string, index *fileIndex, formats types.Types, encoder *zstd.Encoder, errorChannel chan<- error) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
refererUri, err := stripQueryParams(refererToUri(r.Referer())) refererUri, err := stripQueryParams(refererToUri(r.Referer()))
if err != nil { if err != nil {
@ -203,7 +206,7 @@ func serveRoot(paths []string, index *fileIndex, formats types.Types, errorChann
} }
} }
list := fileList(paths, filters, sortOrder, index, formats, errorChannel) list := fileList(paths, filters, sortOrder, index, formats, encoder, errorChannel)
loop: loop:
for timeout := time.After(timeout); ; { for timeout := time.After(timeout); ; {
@ -576,7 +579,12 @@ func ServePage(args []string) error {
} }
}() }()
registerHandler(mux, Prefix, serveRoot(paths, index, formats, errorChannel)) encoder, err := zstd.NewWriter(nil, zstd.WithEncoderLevel(zstd.SpeedBestCompression))
if err != nil {
return err
}
registerHandler(mux, Prefix, serveRoot(paths, index, formats, encoder, errorChannel))
Prefix = strings.TrimSuffix(Prefix, "/") Prefix = strings.TrimSuffix(Prefix, "/")
@ -595,9 +603,9 @@ func ServePage(args []string) error {
registerHandler(mux, Prefix+"/version", serveVersion(errorChannel)) registerHandler(mux, Prefix+"/version", serveVersion(errorChannel))
if Index { if Index {
registerHandler(mux, Prefix+AdminPrefix+"/index/rebuild", serveIndexRebuild(args, index, formats, errorChannel)) registerHandler(mux, Prefix+AdminPrefix+"/index/rebuild", serveIndexRebuild(args, index, formats, encoder, errorChannel))
importIndex(paths, index, formats, errorChannel) importIndex(paths, index, formats, encoder, errorChannel)
} }
if Info { if Info {