Skip to content

Commit

Permalink
Merge branch 'main' into androidfatdst
Browse files Browse the repository at this point in the history
* main:
  lib/fs: Reduce memory usage in xattrs handling (syncthing#9251)
  lib/model: Improve LastSeen handling (syncthing#9256)
  lib/scanner: Record inode change time for directories and symlinks (syncthing#9250)
  lib/api: Improve ignore loading error handling (fixes syncthing#9253) (syncthing#9254)
  gui, man, authors: Update docs, translations, and contributors
  lib/fs: Better equality comparison in mtimefs
  cmd/stcrashreceiver: Add metrics for diskstore inventory
  cmd/stcrashreceiver: Minor cleanup, stricter file permissions
  cmd/stcrashreceiver: Add metrics for incoming reports
  cmd/ursrv: Add metrics for incoming reports
  gui, man, authors: Update docs, translations, and contributors
  gui: Specialize a special-purpose checkbox style (syncthing#9236)
  gui, man, authors: Update docs, translations, and contributors
  build: Support new nested namespaces in Weblate downloads
  lib/model: Acquire fmut lock in ensureIndexHandler (fixes syncthing#9234) (syncthing#9235)
  gui: Allow to translate "unknown device" (syncthing#9229)
  • Loading branch information
calmh committed Dec 5, 2023
2 parents d139f9e + c1ec9a8 commit 48ec761
Show file tree
Hide file tree
Showing 61 changed files with 323 additions and 130 deletions.
2 changes: 2 additions & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Alexander Seiler <seileralex@gmail.com>
Alexandre Alves <alexandrealvesdb.contact@gmail.com>
Alexandre Viau (aviau) <alexandre@alexandreviau.net> <aviau@debian.org>
Aman Gupta <aman@tmm1.net>
Anatoli Babenia <anatoli@rainforce.org>
Anderson Mesquita (andersonvom) <andersonvom@gmail.com>
Andreas Sommer <andreas.sommer87@googlemail.com>
andresvia <andres.via@gmail.com>
Expand Down Expand Up @@ -100,6 +101,7 @@ Dennis Wilson (snnd) <dw@risu.io>
dependabot-preview[bot] <dependabot-preview[bot]@users.noreply.github.com> <27856297+dependabot-preview[bot]@users.noreply.github.com>
dependabot[bot] <dependabot[bot]@users.noreply.github.com> <49699333+dependabot[bot]@users.noreply.github.com>
derekriemer <derek.riemer@colorado.edu>
DerRockWolf <50499906+DerRockWolf@users.noreply.github.com>
desbma <desbma@users.noreply.github.com>
Devon G. Redekopp <devon@redekopp.com>
digital <didev@dinid.net>
Expand Down
17 changes: 14 additions & 3 deletions cmd/stcrashreceiver/diskstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"context"
"io"
"log"
"math"
"os"
"path/filepath"
"sort"
Expand Down Expand Up @@ -40,7 +41,7 @@ type currentFile struct {
}

func (d *diskStore) Serve(ctx context.Context) {
if err := os.MkdirAll(d.dir, 0750); err != nil {
if err := os.MkdirAll(d.dir, 0o700); err != nil {
log.Println("Creating directory:", err)
return
}
Expand All @@ -60,7 +61,7 @@ func (d *diskStore) Serve(ctx context.Context) {
case entry := <-d.inbox:
path := d.fullPath(entry.path)

if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
log.Println("Creating directory:", err)
continue
}
Expand All @@ -75,7 +76,7 @@ func (d *diskStore) Serve(ctx context.Context) {
log.Println("Failed to compress crash report:", err)
continue
}
if err := os.WriteFile(path, buf.Bytes(), 0644); err != nil {
if err := os.WriteFile(path, buf.Bytes(), 0o600); err != nil {
log.Printf("Failed to write %s: %v", entry.path, err)
_ = os.Remove(path)
continue
Expand Down Expand Up @@ -147,6 +148,11 @@ func (d *diskStore) clean() {
if len(d.currentFiles) > 0 {
oldest = time.Since(time.Unix(d.currentFiles[0].mtime, 0)).Truncate(time.Minute)
}

metricDiskstoreFilesTotal.Set(float64(len(d.currentFiles)))
metricDiskstoreBytesTotal.Set(float64(d.currentSize))
metricDiskstoreOldestAgeSeconds.Set(math.Round(oldest.Seconds()))

log.Printf("Clean complete: %d files, %d MB, oldest is %v ago", len(d.currentFiles), d.currentSize>>20, oldest)
}

Expand Down Expand Up @@ -178,6 +184,11 @@ func (d *diskStore) inventory() error {
if len(d.currentFiles) > 0 {
oldest = time.Since(time.Unix(d.currentFiles[0].mtime, 0)).Truncate(time.Minute)
}

metricDiskstoreFilesTotal.Set(float64(len(d.currentFiles)))
metricDiskstoreBytesTotal.Set(float64(d.currentSize))
metricDiskstoreOldestAgeSeconds.Set(math.Round(oldest.Seconds()))

log.Printf("Inventory complete: %d files, %d MB, oldest is %v ago", len(d.currentFiles), d.currentSize>>20, oldest)
return err
}
Expand Down
24 changes: 15 additions & 9 deletions cmd/stcrashreceiver/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ import (
"net/http"
"os"
"path/filepath"
"time"

"github.com/alecthomas/kong"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/syncthing/syncthing/lib/sha256"
"github.com/syncthing/syncthing/lib/ur"

Expand All @@ -33,14 +33,13 @@ import (
const maxRequestSize = 1 << 20 // 1 MiB

type cli struct {
Dir string `help:"Parent directory to store crash and failure reports in" env:"REPORTS_DIR" default:"."`
DSN string `help:"Sentry DSN" env:"SENTRY_DSN"`
Listen string `help:"HTTP listen address" default:":8080" env:"LISTEN_ADDRESS"`
MaxDiskFiles int `help:"Maximum number of reports on disk" default:"100000" env:"MAX_DISK_FILES"`
MaxDiskSizeMB int64 `help:"Maximum disk space to use for reports" default:"1024" env:"MAX_DISK_SIZE_MB"`
CleanInterval time.Duration `help:"Interval between cleaning up old reports" default:"12h" env:"CLEAN_INTERVAL"`
SentryQueue int `help:"Maximum number of reports to queue for sending to Sentry" default:"64" env:"SENTRY_QUEUE"`
DiskQueue int `help:"Maximum number of reports to queue for writing to disk" default:"64" env:"DISK_QUEUE"`
Dir string `help:"Parent directory to store crash and failure reports in" env:"REPORTS_DIR" default:"."`
DSN string `help:"Sentry DSN" env:"SENTRY_DSN"`
Listen string `help:"HTTP listen address" default:":8080" env:"LISTEN_ADDRESS"`
MaxDiskFiles int `help:"Maximum number of reports on disk" default:"100000" env:"MAX_DISK_FILES"`
MaxDiskSizeMB int64 `help:"Maximum disk space to use for reports" default:"1024" env:"MAX_DISK_SIZE_MB"`
SentryQueue int `help:"Maximum number of reports to queue for sending to Sentry" default:"64" env:"SENTRY_QUEUE"`
DiskQueue int `help:"Maximum number of reports to queue for writing to disk" default:"64" env:"DISK_QUEUE"`
}

func main() {
Expand Down Expand Up @@ -72,6 +71,7 @@ func main() {
mux.HandleFunc("/ping", func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("OK"))
})
mux.Handle("/metrics", promhttp.Handler())

if params.DSN != "" {
mux.HandleFunc("/newcrash/failure", handleFailureFn(params.DSN, filepath.Join(params.Dir, "failure_reports")))
Expand All @@ -85,6 +85,11 @@ func main() {

func handleFailureFn(dsn, failureDir string) func(w http.ResponseWriter, req *http.Request) {
return func(w http.ResponseWriter, req *http.Request) {
result := "failure"
defer func() {
metricFailureReportsTotal.WithLabelValues(result).Inc()
}()

lr := io.LimitReader(req.Body, maxRequestSize)
bs, err := io.ReadAll(lr)
req.Body.Close()
Expand Down Expand Up @@ -135,6 +140,7 @@ func handleFailureFn(dsn, failureDir string) func(w http.ResponseWriter, req *ht
log.Println("Failed to send failure report:", err)
} else {
log.Println("Sent failure report:", r.Description)
result = "success"
}
}
}
Expand Down
40 changes: 40 additions & 0 deletions cmd/stcrashreceiver/metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (C) 2023 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

package main

import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)

var (
metricCrashReportsTotal = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: "syncthing",
Subsystem: "crashreceiver",
Name: "crash_reports_total",
}, []string{"result"})
metricFailureReportsTotal = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: "syncthing",
Subsystem: "crashreceiver",
Name: "failure_reports_total",
}, []string{"result"})
metricDiskstoreFilesTotal = promauto.NewGauge(prometheus.GaugeOpts{
Namespace: "syncthing",
Subsystem: "crashreceiver",
Name: "diskstore_files_total",
})
metricDiskstoreBytesTotal = promauto.NewGauge(prometheus.GaugeOpts{
Namespace: "syncthing",
Subsystem: "crashreceiver",
Name: "diskstore_bytes_total",
})
metricDiskstoreOldestAgeSeconds = promauto.NewGauge(prometheus.GaugeOpts{
Namespace: "syncthing",
Subsystem: "crashreceiver",
Name: "diskstore_oldest_age_seconds",
})
)
9 changes: 9 additions & 0 deletions cmd/stcrashreceiver/stcrashreceiver.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ func (r *crashReceiver) serveHead(reportID string, w http.ResponseWriter, _ *htt

// servePut accepts and stores the given report.
func (r *crashReceiver) servePut(reportID string, w http.ResponseWriter, req *http.Request) {
result := "receive_failure"
defer func() {
metricCrashReportsTotal.WithLabelValues(result).Inc()
}()

// Read at most maxRequestSize of report data.
log.Println("Receiving report", reportID)
lr := io.LimitReader(req.Body, maxRequestSize)
Expand All @@ -81,13 +86,17 @@ func (r *crashReceiver) servePut(reportID string, w http.ResponseWriter, req *ht
return
}

result = "success"

// Store the report
if !r.store.Put(reportID, bs) {
log.Println("Failed to store report (queue full):", reportID)
result = "queue_failure"
}

// Send the report to Sentry
if !r.sentry.Send(reportID, userIDFor(req), bs) {
log.Println("Failed to send report to sentry (queue full):", reportID)
result = "sentry_failure"
}
}
26 changes: 26 additions & 0 deletions cmd/ursrv/serve/metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (C) 2023 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

package serve

import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)

var metricReportsTotal = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: "syncthing",
Subsystem: "ursrv",
Name: "reports_total",
}, []string{"version"})

func init() {
metricReportsTotal.WithLabelValues("fail")
metricReportsTotal.WithLabelValues("duplicate")
metricReportsTotal.WithLabelValues("v1")
metricReportsTotal.WithLabelValues("v2")
metricReportsTotal.WithLabelValues("v3")
}
12 changes: 12 additions & 0 deletions cmd/ursrv/serve/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"database/sql"
"embed"
"encoding/json"
"fmt"
"html/template"
"io"
"log"
Expand All @@ -26,6 +27,7 @@ import (

_ "github.com/lib/pq" // PostgreSQL driver
"github.com/oschwald/geoip2-golang"
"github.com/prometheus/client_golang/prometheus/promhttp"
"golang.org/x/text/cases"
"golang.org/x/text/language"

Expand Down Expand Up @@ -196,6 +198,7 @@ func (cli *CLI) Run() error {
http.HandleFunc("/performance.json", srv.performanceHandler)
http.HandleFunc("/blockstats.json", srv.blockStatsHandler)
http.HandleFunc("/locations.json", srv.locationsHandler)
http.Handle("/metrics", promhttp.Handler())
http.Handle("/static/", http.FileServer(http.FS(statics)))

go srv.cacheRefresher()
Expand Down Expand Up @@ -289,6 +292,12 @@ func (s *server) locationsHandler(w http.ResponseWriter, _ *http.Request) {
}

func (s *server) newDataHandler(w http.ResponseWriter, r *http.Request) {
version := "fail"
defer func() {
// Version is "fail", "duplicate", "v2", "v3", ...
metricReportsTotal.WithLabelValues(version).Inc()
}()

defer r.Body.Close()

addr := r.Header.Get("X-Forwarded-For")
Expand Down Expand Up @@ -334,6 +343,7 @@ func (s *server) newDataHandler(w http.ResponseWriter, r *http.Request) {
if err.Error() == `pq: duplicate key value violates unique constraint "uniqueidjsonindex"` {
// We already have a report today for the same unique ID; drop
// this one without complaining.
version = "duplicate"
return
}
log.Println("insert:", err)
Expand All @@ -343,6 +353,8 @@ func (s *server) newDataHandler(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Database Error", http.StatusInternalServerError)
return
}

version = fmt.Sprintf("v%d", rep.URVersion)
}

func (s *server) summaryHandler(w http.ResponseWriter, r *http.Request) {
Expand Down
3 changes: 2 additions & 1 deletion gui/default/assets/css/overrides.css
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@ table.table-auto td {
max-width: 0px;
}

td input[type="checkbox"] {
/* Tag name is needed for selector to be specific enough to override Bootstrap style */
input[type="checkbox"].extended-attributes-filter-rule-checkbox {
margin-top: 13px;
}

Expand Down
2 changes: 1 addition & 1 deletion gui/default/assets/lang/lang-bg.json
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,6 @@
"QR code": "Код за QR",
"QUIC LAN": "QUIC LAN",
"QUIC WAN": "QUIC WAN",
"QUIC connections are in most cases considered suboptimal": "В повечето случаи връзките през протокола QUIC се считат за неоптимални",
"Quick guide to supported patterns": "Кратък наръчник на поддържаните шаблони",
"Random": "Произволен",
"Receive Encrypted": "Приема шифровани данни",
Expand Down Expand Up @@ -546,6 +545,7 @@
"light": "Светла"
}
},
"unknown device": "непознато устройство",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} споделя папката „{{folder}}“.",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} споделя папката „{{folderlabel}}“ ({{folder}}).",
"{%reintroducer%} might reintroduce this device.": "Поръчителят {{reintroducer}} може отново да предложи това устройство."
Expand Down
1 change: 0 additions & 1 deletion gui/default/assets/lang/lang-ca.json
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,6 @@
"QR code": "Codi QR",
"QUIC LAN": "Connexió QUIC LAN",
"QUIC WAN": "Connexió QUIC WAN",
"QUIC connections are in most cases considered suboptimal": "En la majoria dels casos, les connexions QUIC es consideren subòptimes",
"Quick guide to supported patterns": "Guia ràpida per als possibles patrons",
"Random": "Aleatori",
"Receive Encrypted": "Rebre xifrat",
Expand Down
1 change: 0 additions & 1 deletion gui/default/assets/lang/lang-ca@valencia.json
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,6 @@
"QR code": "Codi QR",
"QUIC LAN": "Xarxa QUIC LAN",
"QUIC WAN": "Xarxa QUIC WAN",
"QUIC connections are in most cases considered suboptimal": "En la majoria dels casos, les connexions QUIC es consideren subòptimes",
"Quick guide to supported patterns": "Guía ràpida de patrons suportats",
"Random": "Aleatori",
"Receive Encrypted": "Rebre xifrat",
Expand Down
1 change: 0 additions & 1 deletion gui/default/assets/lang/lang-cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,6 @@
"Preview": "Náhled",
"Preview Usage Report": "Náhled hlášení o využívání",
"QR code": "QR kód",
"QUIC connections are in most cases considered suboptimal": "QUIC připojení jsou ve většině případů považována za suboptimální",
"Quick guide to supported patterns": "Rychlá nápověda k podporovaným vzorům",
"Random": "Náhodné",
"Receive Encrypted": "Přijmout zašifrované",
Expand Down
1 change: 0 additions & 1 deletion gui/default/assets/lang/lang-da.json
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,6 @@
"QR code": "QR-kode",
"QUIC LAN": "QUIC LAN",
"QUIC WAN": "QUIC WAN",
"QUIC connections are in most cases considered suboptimal": "QUIC-forbindelser anses i de fleste tilfælde for at være mindre optimale",
"Quick guide to supported patterns": "Kvikguide til understøttede mønstre",
"Random": "Tilfældig",
"Receive Encrypted": "Modtag krypteret",
Expand Down
2 changes: 1 addition & 1 deletion gui/default/assets/lang/lang-de.json
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,6 @@
"QR code": "QR-Code",
"QUIC LAN": "QUIC LAN",
"QUIC WAN": "QUIC WAN",
"QUIC connections are in most cases considered suboptimal": "QUIC-Verbindungen sind in den meisten Fällen suboptimal",
"Quick guide to supported patterns": "Schnellanleitung zu den unterstützten Mustern",
"Random": "Zufall",
"Receive Encrypted": "Empfange verschlüsselt",
Expand Down Expand Up @@ -546,6 +545,7 @@
"light": "Hell"
}
},
"unknown device": "unbekanntes Gerät",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} möchte den Ordner „{{folder}}“ teilen.",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} möchte den Ordner „{{folderlabel}}“ ({{folder}}) teilen.",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} könnte dieses Gerät wieder einführen."
Expand Down
1 change: 0 additions & 1 deletion gui/default/assets/lang/lang-en-AU.json
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,6 @@
"QR code": "QR code",
"QUIC LAN": "QUIC LAN",
"QUIC WAN": "QUIC WAN",
"QUIC connections are in most cases considered suboptimal": "QUIC connections are in most cases considered suboptimal",
"Quick guide to supported patterns": "Quick guide to supported patterns",
"Random": "Random",
"Receive Encrypted": "Receive Encrypted",
Expand Down
2 changes: 1 addition & 1 deletion gui/default/assets/lang/lang-en-GB.json
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,6 @@
"QR code": "QR code",
"QUIC LAN": "QUIC LAN",
"QUIC WAN": "QUIC WAN",
"QUIC connections are in most cases considered suboptimal": "QUIC connections are in most cases considered suboptimal",
"Quick guide to supported patterns": "Quick guide to supported patterns",
"Random": "Random",
"Receive Encrypted": "Receive Encrypted",
Expand Down Expand Up @@ -546,6 +545,7 @@
"light": "Light"
}
},
"unknown device": "unknown device",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\".",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} wants to share folder \"{{folderlabel}}\" ({{folder}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
Expand Down
1 change: 1 addition & 0 deletions gui/default/assets/lang/lang-en.json
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,7 @@
"light": "Light"
}
},
"unknown device": "unknown device",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\".",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} wants to share folder \"{{folderlabel}}\" ({{folder}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
Expand Down

0 comments on commit 48ec761

Please sign in to comment.