181 lines
4.8 KiB
Go
181 lines
4.8 KiB
Go
package artifact
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"mime"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"testing"
|
|
|
|
"gitea.maximumdirect.net/eric/promptkit/internal/domain"
|
|
)
|
|
|
|
var artifactFileReadBenchmarkSink *domain.Artifact
|
|
|
|
func BenchmarkArtifactFileRead(b *testing.B) {
|
|
for _, fixture := range makeArtifactReadBenchmarkFiles(b) {
|
|
b.Run(fixture.name, func(b *testing.B) {
|
|
reader := &fileReader{open: openArtifactFile}
|
|
ref := domain.ArtifactRef{Type: domain.ArtifactRefFile, URI: fixture.path}
|
|
checkArtifactFileRead(b, reader, ref, fixture.body)
|
|
|
|
b.ReportAllocs()
|
|
b.SetBytes(int64(len(fixture.body)))
|
|
b.ResetTimer()
|
|
for range b.N {
|
|
artifact, err := reader.Read(context.Background(), ref)
|
|
if err != nil {
|
|
b.Fatalf("read artifact: %v", err)
|
|
}
|
|
artifactFileReadBenchmarkSink = artifact
|
|
}
|
|
b.StopTimer()
|
|
runtime.KeepAlive(artifactFileReadBenchmarkSink)
|
|
})
|
|
}
|
|
}
|
|
|
|
// BenchmarkRetainedArtifactFileRead measures live artifact bodies when an
|
|
// opened file reports a size larger than the content that reaches EOF.
|
|
func BenchmarkRetainedArtifactFileRead(b *testing.B) {
|
|
for _, fixture := range []struct {
|
|
name string
|
|
body []byte
|
|
}{
|
|
{name: "small", body: bytes.Repeat([]byte("x"), 256<<10)},
|
|
{name: "empty", body: []byte{}},
|
|
} {
|
|
b.Run(fixture.name, func(b *testing.B) {
|
|
benchmarkRetainedArtifactFileRead(b, fixture.body)
|
|
})
|
|
}
|
|
}
|
|
|
|
func benchmarkRetainedArtifactFileRead(b *testing.B, body []byte) {
|
|
path := filepath.Join(b.TempDir(), "overreported.txt")
|
|
if err := os.WriteFile(path, body, 0o600); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
mime.TypeByExtension(filepath.Ext(path))
|
|
|
|
reader := &fileReader{open: func(path string) (artifactFile, error) {
|
|
file, err := os.Open(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &sizeReportingArtifactFile{artifactFile: file, reportedSize: 16 << 20}, nil
|
|
}}
|
|
ref := domain.ArtifactRef{Type: domain.ArtifactRefFile, URI: path}
|
|
checkArtifactFileRead(b, reader, ref, body)
|
|
|
|
const retainedBatchSize = 4
|
|
retained := make([]*domain.Artifact, retainedBatchSize)
|
|
artifactFileReadBenchmarkSink = nil
|
|
runtime.GC()
|
|
var setupMemory runtime.MemStats
|
|
runtime.ReadMemStats(&setupMemory)
|
|
runtime.KeepAlive(reader)
|
|
runtime.KeepAlive(ref)
|
|
|
|
b.ReportAllocs()
|
|
b.SetBytes(int64(len(body)))
|
|
b.ResetTimer()
|
|
for iteration := 0; iteration < b.N; iteration++ {
|
|
artifact, err := reader.Read(context.Background(), ref)
|
|
if err != nil {
|
|
b.Fatalf("read retained artifact: %v", err)
|
|
}
|
|
retained[iteration%len(retained)] = artifact
|
|
}
|
|
b.StopTimer()
|
|
for i, artifact := range retained {
|
|
if artifact != nil {
|
|
continue
|
|
}
|
|
artifact, err := reader.Read(context.Background(), ref)
|
|
if err != nil {
|
|
b.Fatalf("fill retained artifact batch: %v", err)
|
|
}
|
|
retained[i] = artifact
|
|
}
|
|
runtime.GC()
|
|
var retainedMemory runtime.MemStats
|
|
runtime.ReadMemStats(&retainedMemory)
|
|
b.ReportMetric(float64(int64(retainedMemory.HeapAlloc)-int64(setupMemory.HeapAlloc)), "retained-B")
|
|
runtime.KeepAlive(retained)
|
|
runtime.KeepAlive(reader)
|
|
runtime.KeepAlive(ref)
|
|
}
|
|
|
|
type artifactReadBenchmarkFile struct {
|
|
name string
|
|
path string
|
|
body []byte
|
|
}
|
|
|
|
func makeArtifactReadBenchmarkFiles(b *testing.B) []artifactReadBenchmarkFile {
|
|
b.Helper()
|
|
fixtures := []struct {
|
|
name string
|
|
size int
|
|
}{
|
|
{name: "small_4KiB", size: 4 << 10},
|
|
{name: "1MiB", size: 1 << 20},
|
|
{name: "large_4MiB", size: 4 << 20},
|
|
}
|
|
results := make([]artifactReadBenchmarkFile, 0, len(fixtures))
|
|
for _, fixture := range fixtures {
|
|
body := bytes.Repeat([]byte("x"), fixture.size)
|
|
path := filepath.Join(b.TempDir(), fixture.name+".txt")
|
|
if err := os.WriteFile(path, body, 0o600); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
mime.TypeByExtension(filepath.Ext(path))
|
|
results = append(results, artifactReadBenchmarkFile{
|
|
name: fixture.name,
|
|
path: path,
|
|
body: body,
|
|
})
|
|
}
|
|
return results
|
|
}
|
|
|
|
func checkArtifactFileRead(b *testing.B, reader Reader, ref domain.ArtifactRef, want []byte) {
|
|
b.Helper()
|
|
artifact, err := reader.Read(context.Background(), ref)
|
|
if err != nil {
|
|
b.Fatalf("check artifact read: %v", err)
|
|
}
|
|
if !bytes.Equal(artifact.Body, want) || artifact.Size != int64(len(want)) {
|
|
b.Fatalf("artifact size=%d body length=%d, want %d", artifact.Size, len(artifact.Body), len(want))
|
|
}
|
|
wantHash := fmt.Sprintf("%x", sha256.Sum256(want))
|
|
if artifact.Hash != wantHash {
|
|
b.Fatalf("artifact hash=%q, want %q", artifact.Hash, wantHash)
|
|
}
|
|
}
|
|
|
|
type sizeReportingArtifactFile struct {
|
|
artifactFile
|
|
reportedSize int64
|
|
}
|
|
|
|
func (file *sizeReportingArtifactFile) Stat() (os.FileInfo, error) {
|
|
info, err := file.artifactFile.Stat()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return reportedArtifactFileInfo{FileInfo: info, size: file.reportedSize}, nil
|
|
}
|
|
|
|
type reportedArtifactFileInfo struct {
|
|
os.FileInfo
|
|
size int64
|
|
}
|
|
|
|
func (info reportedArtifactFileInfo) Size() int64 { return info.size }
|