Some performance and memory improvements

This commit is contained in:
Jan-Lukas Else 2022-01-26 12:02:12 +01:00
parent 48f2ac888b
commit 763188169f
11 changed files with 119 additions and 92 deletions

View File

@ -22,16 +22,14 @@ const (
)
type cache struct {
g singleflight.Group
c *ristretto.Cache
cfg *configCache
g singleflight.Group
c *ristretto.Cache
}
func (a *goBlog) initCache() (err error) {
a.cache = &cache{
cfg: a.cfg.Cache,
}
if a.cache.cfg != nil && !a.cache.cfg.Enable {
a.cache = &cache{}
if a.cfg.Cache != nil && !a.cfg.Cache.Enable {
// Cache disabled
return nil
}
a.cache.c, err = ristretto.NewCache(&ristretto.Config{
@ -50,13 +48,8 @@ func cacheLoggedIn(next http.Handler) http.Handler {
func (a *goBlog) cacheMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if a.cache.c == nil {
// No cache configured
next.ServeHTTP(w, r)
return
}
// Do checks
if !cacheable(r) {
if a.cache.c == nil || !cacheable(r) {
next.ServeHTTP(w, r)
return
}
@ -79,7 +72,7 @@ func (a *goBlog) cacheMiddleware(next http.Handler) http.Handler {
})
ci := cacheInterface.(*cacheItem)
// copy and set headers
a.cache.setHeaders(w, ci)
a.setCacheHeaders(w, ci)
// check conditional request
if ifNoneMatchHeader := r.Header.Get("If-None-Match"); ifNoneMatchHeader != "" && ifNoneMatchHeader == ci.eTag {
// send 304
@ -129,7 +122,7 @@ func cacheKey(r *http.Request) string {
return buf.String()
}
func (c *cache) setHeaders(w http.ResponseWriter, cache *cacheItem) {
func (a *goBlog) setCacheHeaders(w http.ResponseWriter, cache *cacheItem) {
// Copy headers
for k, v := range cache.header.Clone() {
w.Header()[k] = v
@ -141,7 +134,8 @@ func (c *cache) setHeaders(w http.ResponseWriter, cache *cacheItem) {
if cache.expiration != 0 {
w.Header().Set("Cache-Control", fmt.Sprintf("public,max-age=%d,stale-while-revalidate=%d", cache.expiration, cache.expiration))
} else {
w.Header().Set("Cache-Control", fmt.Sprintf("public,max-age=%d,s-max-age=%d,stale-while-revalidate=%d", c.cfg.Expiration, c.cfg.Expiration/3, c.cfg.Expiration))
exp := a.cfg.Cache.Expiration
w.Header().Set("Cache-Control", fmt.Sprintf("public,max-age=%d,s-max-age=%d,stale-while-revalidate=%d", exp, exp/3, exp))
}
}
}

View File

@ -12,6 +12,7 @@ import (
"strings"
"time"
"go.goblog.app/app/pkgs/bufferpool"
"go.goblog.app/app/pkgs/contenttype"
"gopkg.in/yaml.v3"
ws "nhooyr.io/websocket"
@ -71,9 +72,11 @@ func (a *goBlog) createMarkdownPreview(blog string, markdown []byte) (rendered [
p.RenderedTitle = a.renderMdTitle(t)
}
// Render post
var hb htmlBuilder
a.renderEditorPreview(&hb, a.cfg.Blogs[blog], p)
rendered = hb.Bytes()
buf := bufferpool.Get()
hb := newHtmlBuilder(buf)
a.renderEditorPreview(hb, a.cfg.Blogs[blog], p)
rendered = buf.Bytes()
bufferpool.Put(buf)
return
}

2
go.mod
View File

@ -61,7 +61,7 @@ require (
golang.org/x/text v0.3.7
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
nhooyr.io/websocket v1.8.7
tailscale.com v1.20.2
tailscale.com v1.20.3
// main
willnorris.com/go/microformats v1.1.2-0.20210827044458-ff2a6ae41971
)

4
go.sum
View File

@ -823,8 +823,8 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78 h1:SqYE5+A2qvRhErbsXFfUEUmpWEKxxRSMgGLkvRAFOV4=
tailscale.com v1.20.2 h1:Vk6SVGmczDFSx+PYjbKMa8gIC9Y9Rq+vT1XoZni+fkA=
tailscale.com v1.20.2/go.mod h1:kjVy3ji2OH5lZhPLIIRacoY3CN4Bo3Yyb2mtoM8nfJ4=
tailscale.com v1.20.3 h1:C3g2AgmQaOi0YT5dAal9mslugPXMxwj0EXY7YfL2QrA=
tailscale.com v1.20.3/go.mod h1:kjVy3ji2OH5lZhPLIIRacoY3CN4Bo3Yyb2mtoM8nfJ4=
willnorris.com/go/microformats v1.1.2-0.20210827044458-ff2a6ae41971 h1:b4juh5znIpBA1KnzHMP0UB4Cs+3/0b0XfchkWE81FXw=
willnorris.com/go/microformats v1.1.2-0.20210827044458-ff2a6ae41971/go.mod h1:kvVnWrkkEscVAIITCEoiTX66Hcyg59C7q0E49mb9TJ0=
willnorris.com/go/webmention v0.0.0-20211028201829-b0044f1a24d0 h1:3/ozQ2qGZat82ON3AYMTot3gCg/vU7tgn/LYSJbkVPM=

View File

@ -3,6 +3,7 @@ package main
import (
"bytes"
"html/template"
"io"
marktag "git.jlel.se/jlelse/goldmark-mark"
chromahtml "github.com/alecthomas/chroma/formatters/html"
@ -15,6 +16,7 @@ import (
"github.com/yuin/goldmark/renderer"
"github.com/yuin/goldmark/renderer/html"
"github.com/yuin/goldmark/util"
"go.goblog.app/app/pkgs/bufferpool"
)
func (a *goBlog) initMarkdown() {
@ -76,13 +78,20 @@ func (a *goBlog) initMarkdown() {
}
func (a *goBlog) renderMarkdown(source string, absoluteLinks bool) (rendered []byte, err error) {
var buffer bytes.Buffer
buffer := bufferpool.Get()
a.renderMarkdownToWriter(buffer, source, absoluteLinks)
rendered = buffer.Bytes()
bufferpool.Put(buffer)
return
}
func (a *goBlog) renderMarkdownToWriter(w io.Writer, source string, absoluteLinks bool) (err error) {
if absoluteLinks {
err = a.absoluteMd.Convert([]byte(source), &buffer)
err = a.absoluteMd.Convert([]byte(source), w)
} else {
err = a.md.Convert([]byte(source), &buffer)
err = a.md.Convert([]byte(source), w)
}
return buffer.Bytes(), err
return err
}
func (a *goBlog) renderMarkdownAsHTML(source string, absoluteLinks bool) (rendered template.HTML, err error) {
@ -113,8 +122,9 @@ func (a *goBlog) renderMdTitle(s string) string {
if s == "" {
return ""
}
var buffer bytes.Buffer
err := a.titleMd.Convert([]byte(s), &buffer)
buffer := bufferpool.Get()
defer bufferpool.Put(buffer)
err := a.titleMd.Convert([]byte(s), buffer)
if err != nil {
return ""
}

View File

@ -0,0 +1,29 @@
package bufferpool
import (
"bytes"
"sync"
)
var bufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
var poolMutex sync.Mutex
func Get() *bytes.Buffer {
poolMutex.Lock()
defer poolMutex.Unlock()
return bufferPool.Get().(*bytes.Buffer)
}
func Put(bufs ...*bytes.Buffer) {
poolMutex.Lock()
defer poolMutex.Unlock()
for _, buf := range bufs {
buf.Reset()
bufferPool.Put(buf)
}
}

View File

@ -1,7 +1,6 @@
package main
import (
"bytes"
"html/template"
"net/http"
"os"
@ -9,6 +8,7 @@ import (
"path/filepath"
"strings"
"go.goblog.app/app/pkgs/bufferpool"
"go.goblog.app/app/pkgs/contenttype"
)
@ -33,9 +33,12 @@ func (a *goBlog) initRendering() error {
"html": wrapStringAsHTML,
// Code based rendering
"tor": func(rd *renderData) template.HTML {
var hb htmlBuilder
a.renderTorNotice(&hb, rd)
return hb.html()
buf := bufferpool.Get()
hb := newHtmlBuilder(buf)
a.renderTorNotice(hb, rd)
res := template.HTML(buf.String())
bufferpool.Put(buf)
return res
},
// Others
"dateformat": dateFormat,
@ -103,16 +106,14 @@ func (a *goBlog) renderWithStatusCode(w http.ResponseWriter, r *http.Request, st
// Set content type
w.Header().Set(contentType, contenttype.HTMLUTF8)
// Render template and write minified HTML
var templateBuffer bytes.Buffer
if err := a.templates[template].ExecuteTemplate(&templateBuffer, template, data); err != nil {
buf := bufferpool.Get()
defer bufferpool.Put(buf)
if err := a.templates[template].ExecuteTemplate(buf, template, data); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(statusCode)
if err := a.min.Minify(contenttype.HTML, w, &templateBuffer); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
_ = a.min.Minify(contenttype.HTML, w, buf)
}
func (a *goBlog) renderNew(w http.ResponseWriter, r *http.Request, f func(*htmlBuilder, *renderData), data *renderData) {

16
ui.go
View File

@ -71,7 +71,7 @@ func (a *goBlog) renderBase(hb *htmlBuilder, rd *renderData, title, main func(hb
// Announcement
if ann := rd.Blog.Announcement; ann != nil && ann.Text != "" {
hb.writeElementOpen("div", "id", "announcement", "data-nosnippet", "")
hb.writeHtml(a.safeRenderMarkdownAsHTML(ann.Text))
_ = a.renderMarkdownToWriter(hb, ann.Text, false)
hb.writeElementClose("div")
}
// Header
@ -270,7 +270,7 @@ func (a *goBlog) renderSearch(hb *htmlBuilder, rd *renderData) {
// Description
if sc.Description != "" {
titleOrDesc = true
hb.writeHtml(a.safeRenderMarkdownAsHTML(sc.Description))
_ = a.renderMarkdownToWriter(hb, sc.Description, false)
}
if titleOrDesc {
hb.writeElementOpen("hr")
@ -381,7 +381,7 @@ func (a *goBlog) renderIndex(hb *htmlBuilder, rd *renderData) {
// Description
if id.description != "" {
titleOrDesc = true
hb.writeHtml(a.safeRenderMarkdownAsHTML(id.description))
_ = a.renderMarkdownToWriter(hb, id.description, false)
}
if titleOrDesc {
hb.writeElementOpen("hr")
@ -445,7 +445,7 @@ func (a *goBlog) renderBlogStats(hb *htmlBuilder, rd *renderData) {
}
// Description
if bs.Description != "" {
hb.writeHtml(a.safeRenderMarkdownAsHTML(bs.Description))
_ = a.renderMarkdownToWriter(hb, bs.Description, false)
}
// Table
hb.writeElementOpen("p", "id", "loading", "data-table", bsd.tableUrl)
@ -663,7 +663,7 @@ func (a *goBlog) renderBlogroll(hb *htmlBuilder, rd *renderData) {
// Description
if bd.description != "" {
hb.writeElementOpen("p")
hb.writeHtml(a.safeRenderMarkdownAsHTML(bd.description))
_ = a.renderMarkdownToWriter(hb, bd.description, false)
hb.writeElementClose("p")
}
// Download button
@ -745,7 +745,7 @@ func (a *goBlog) renderContact(hb *htmlBuilder, rd *renderData) {
}
// Description
if cd.description != "" {
hb.writeHtml(a.safeRenderMarkdownAsHTML(cd.description))
_ = a.renderMarkdownToWriter(hb, cd.description, false)
}
// Form
hb.writeElementOpen("form", "class", "fw p", "method", "post")
@ -760,7 +760,7 @@ func (a *goBlog) renderContact(hb *htmlBuilder, rd *renderData) {
hb.writeElementClose("textarea")
// Send
if cd.privacy != "" {
hb.writeHtml(a.safeRenderMarkdownAsHTML(cd.privacy))
_ = a.renderMarkdownToWriter(hb, cd.privacy, false)
hb.writeElementOpen("input", "type", "submit", "value", a.ts.GetTemplateStringVariant(rd.Blog.Lang, "contactagreesend"))
} else {
hb.writeElementOpen("input", "type", "submit", "value", a.ts.GetTemplateStringVariant(rd.Blog.Lang, "contactsend"))
@ -837,7 +837,7 @@ func (a *goBlog) renderTaxonomy(hb *htmlBuilder, rd *renderData) {
}
// Description
if trd.taxonomy.Description != "" {
hb.writeHtml(a.safeRenderMarkdownAsHTML(trd.taxonomy.Description))
_ = a.renderMarkdownToWriter(hb, trd.taxonomy.Description, false)
}
// List
for _, valGroup := range trd.valueGroups {

View File

@ -38,7 +38,7 @@ func (a *goBlog) renderSummary(hb *htmlBuilder, bc *configBlog, p *post, typ sum
photos := a.photoLinks(p)
if typ == photoSummary && len(photos) > 0 {
for _, photo := range photos {
hb.write(string(a.safeRenderMarkdownAsHTML(fmt.Sprintf("![](%s)", photo))))
_ = a.renderMarkdownToWriter(hb, fmt.Sprintf("![](%s)", photo), false)
}
}
// Post meta

View File

@ -1,7 +1,6 @@
package main
import (
"bytes"
"fmt"
"html/template"
"io"
@ -9,8 +8,7 @@ import (
)
type htmlBuilder struct {
w io.Writer
buf bytes.Buffer
w io.Writer
}
func newHtmlBuilder(w io.Writer) *htmlBuilder {
@ -20,10 +18,7 @@ func newHtmlBuilder(w io.Writer) *htmlBuilder {
}
func (h *htmlBuilder) getWriter() io.Writer {
if h.w != nil {
return h.w
}
return &h.buf
return h.w
}
func (h *htmlBuilder) Write(p []byte) (int, error) {
@ -34,22 +29,6 @@ func (h *htmlBuilder) WriteString(s string) (int, error) {
return io.WriteString(h.getWriter(), s)
}
func (h *htmlBuilder) Read(p []byte) (int, error) {
return h.buf.Read(p)
}
func (h *htmlBuilder) String() string {
return h.buf.String()
}
func (h *htmlBuilder) Bytes() []byte {
return h.buf.Bytes()
}
func (h *htmlBuilder) html() template.HTML {
return template.HTML(h.String())
}
func (h *htmlBuilder) write(s string) {
_, _ = h.WriteString(s)
}

View File

@ -1,7 +1,7 @@
package main
import (
"html/template"
"bytes"
"io"
"os"
"strings"
@ -14,7 +14,6 @@ import (
var _ io.Writer = &htmlBuilder{}
var _ io.StringWriter = &htmlBuilder{}
var _ io.Reader = &htmlBuilder{}
func Test_renderPostTax(t *testing.T) {
app := &goBlog{
@ -30,13 +29,16 @@ func Test_renderPostTax(t *testing.T) {
},
}
var hb htmlBuilder
app.renderPostTax(&hb, p, app.cfg.Blogs["default"])
res := hb.html()
_, err := goquery.NewDocumentFromReader(strings.NewReader(string(res)))
buf := &bytes.Buffer{}
hb := newHtmlBuilder(buf)
app.renderPostTax(hb, p, app.cfg.Blogs["default"])
res := buf.String()
_, err := goquery.NewDocumentFromReader(strings.NewReader(res))
require.NoError(t, err)
assert.Equal(t, template.HTML("<p><strong>Tags</strong>: <a class=\"p-category\" rel=\"tag\" href=\"/tags/bar\">Bar</a>, <a class=\"p-category\" rel=\"tag\" href=\"/tags/foo\">Foo</a></p>"), res)
assert.Equal(t, "<p><strong>Tags</strong>: <a class=\"p-category\" rel=\"tag\" href=\"/tags/bar\">Bar</a>, <a class=\"p-category\" rel=\"tag\" href=\"/tags/foo\">Foo</a></p>", res)
}
func Test_renderOldContentWarning(t *testing.T) {
@ -51,13 +53,16 @@ func Test_renderOldContentWarning(t *testing.T) {
Published: "2018-01-01",
}
var hb htmlBuilder
app.renderOldContentWarning(&hb, p, app.cfg.Blogs["default"])
res := hb.html()
_, err := goquery.NewDocumentFromReader(strings.NewReader(string(res)))
buf := &bytes.Buffer{}
hb := newHtmlBuilder(buf)
app.renderOldContentWarning(hb, p, app.cfg.Blogs["default"])
res := buf.String()
_, err := goquery.NewDocumentFromReader(strings.NewReader(res))
require.NoError(t, err)
assert.Equal(t, template.HTML("<strong class=\"p border-top border-bottom\">⚠️ This entry is already over one year old. It may no longer be up to date. Opinions may have changed.</strong>"), res)
assert.Equal(t, "<strong class=\"p border-top border-bottom\">⚠️ This entry is already over one year old. It may no longer be up to date. Opinions may have changed.</strong>", res)
}
func Test_renderInteractions(t *testing.T) {
@ -112,16 +117,19 @@ func Test_renderInteractions(t *testing.T) {
err = app.db.approveWebmentionId(2)
require.NoError(t, err)
var hb htmlBuilder
app.renderInteractions(&hb, app.cfg.Blogs["default"], "https://example.com/testpost1")
res := hb.html()
_, err = goquery.NewDocumentFromReader(strings.NewReader(string(res)))
buf := &bytes.Buffer{}
hb := newHtmlBuilder(buf)
app.renderInteractions(hb, app.cfg.Blogs["default"], "https://example.com/testpost1")
res := buf.Bytes()
_, err = goquery.NewDocumentFromReader(bytes.NewReader(res))
require.NoError(t, err)
expected, err := os.ReadFile("testdata/interactionstest.html")
require.NoError(t, err)
assert.Equal(t, template.HTML(expected), res)
assert.Equal(t, expected, res)
}
func Test_renderAuthor(t *testing.T) {
@ -134,11 +142,14 @@ func Test_renderAuthor(t *testing.T) {
_ = app.initDatabase(false)
app.initComponents(false)
var hb htmlBuilder
app.renderAuthor(&hb)
res := hb.html()
_, err := goquery.NewDocumentFromReader(strings.NewReader(string(res)))
buf := &bytes.Buffer{}
hb := newHtmlBuilder(buf)
app.renderAuthor(hb)
res := buf.String()
_, err := goquery.NewDocumentFromReader(strings.NewReader(res))
require.NoError(t, err)
assert.Equal(t, template.HTML("<div class=\"p-author h-card hide\"><data class=\"u-photo\" value=\"https://example.com/picture.jpg\"></data><a class=\"p-name u-url\" rel=\"me\" href=\"/\">John Doe</a></div>"), res)
assert.Equal(t, "<div class=\"p-author h-card hide\"><data class=\"u-photo\" value=\"https://example.com/picture.jpg\"></data><a class=\"p-name u-url\" rel=\"me\" href=\"/\">John Doe</a></div>", res)
}