mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-10-28 11:22:25 -05:00
Merge branch 'main' into interaction_policies_forward_compat
This commit is contained in:
commit
82780b1a89
207 changed files with 9302 additions and 2275 deletions
65
cmd/gotosocial/action/migration/run.go
Normal file
65
cmd/gotosocial/action/migration/run.go
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package migration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"code.superseriousbusiness.org/gotosocial/cmd/gotosocial/action"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/db/bundb"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/log"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/state"
|
||||
)
|
||||
|
||||
// Run will initialize the database, running any available migrations.
|
||||
var Run action.GTSAction = func(ctx context.Context) error {
|
||||
var state state.State
|
||||
|
||||
defer func() {
|
||||
if state.DB != nil {
|
||||
// Lastly, if database service was started,
|
||||
// ensure it gets closed now all else stopped.
|
||||
if err := state.DB.Close(); err != nil {
|
||||
log.Errorf(ctx, "error stopping database: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Finally reached end of shutdown.
|
||||
log.Info(ctx, "done! exiting...")
|
||||
}()
|
||||
|
||||
// Initialize caches
|
||||
state.Caches.Init()
|
||||
if err := state.Caches.Start(); err != nil {
|
||||
return fmt.Errorf("error starting caches: %w", err)
|
||||
}
|
||||
|
||||
log.Info(ctx, "starting db service...")
|
||||
|
||||
// Open connection to the database now caches started.
|
||||
dbService, err := bundb.NewBunDBService(ctx, &state)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating dbservice: %s", err)
|
||||
}
|
||||
|
||||
// Set DB on state.
|
||||
state.DB = dbService
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -55,6 +55,7 @@ func main() {
|
|||
rootCmd.AddCommand(serverCommands())
|
||||
rootCmd.AddCommand(debugCommands())
|
||||
rootCmd.AddCommand(adminCommands())
|
||||
rootCmd.AddCommand(migrationCommands())
|
||||
|
||||
// Testrigcmd will only be set when debug is enabled.
|
||||
if testrigCmd := testrigCommands(); testrigCmd != nil {
|
||||
|
|
|
|||
43
cmd/gotosocial/migrations.go
Normal file
43
cmd/gotosocial/migrations.go
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"code.superseriousbusiness.org/gotosocial/cmd/gotosocial/action/migration"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// migrationCommands returns the 'migrations' subcommand
|
||||
func migrationCommands() *cobra.Command {
|
||||
migrationCmd := &cobra.Command{
|
||||
Use: "migrations",
|
||||
Short: "gotosocial migrations-related tasks",
|
||||
}
|
||||
migrationRunCmd := &cobra.Command{
|
||||
Use: "run",
|
||||
Short: "starts and stops the database, running any outstanding migrations",
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return preRun(preRunArgs{cmd: cmd})
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return run(cmd.Context(), migration.Run)
|
||||
},
|
||||
}
|
||||
migrationCmd.AddCommand(migrationRunCmd)
|
||||
return migrationCmd
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
# Scraper Deterrence
|
||||
|
||||
GoToSocial provides an optional proof-of-work based scraper and automated HTTP client deterrence that can be enabled on profile and status web views. The way
|
||||
it works is that it generates a unique but deterministic challenge for each incoming HTTP request based on client information and current time, that-is a hex encoded SHA256 hash, and asks the client to find an addition to a portion of this that will generate a hex encoded SHA256 hash with a pre-determined number of leading '0' characters. This is served to the client as a minimal holding page with a single JavaScript worker that computes a solution to this.
|
||||
it works is that it generates a unique but deterministic challenge for each incoming HTTP request based on client information and current time, that-is a hex encoded SHA256 hash. It then asks the client to find an integer addition to a portion of this that will generate an expected encoded hash result. This is served to the client as a minimal holding page with a single JavaScript worker that computes a solution to this.
|
||||
|
||||
The number of required leading '0' characters can be configured to your liking, where higher values take longer to solve, and lower values take less. But this is not exact, as the challenges themselves are random, so you can only effect the **average amount of time** it may take. If your challenges take too long to solve, you may deter users from accessing your web pages. And conversely, the longer it takes for a solution to be found, the more you'll be incurring costs for scrapers (and in some cases, causing their operation to time-out). That balance is up to you to configure, hence why this is an advanced feature.
|
||||
The number of hash encode rounds the client is required to complete may be configured, where high values will take the client longer to find a solution and vice-versa. We also instill a certain amount of jitter to make it harder for scrapers to "game" the algorithm. If your challenges take too long to solve, you may deter users from accessing your web pages. And conversely, the longer it takes for a solution to be found, the more you'll be incurring costs for scrapers (and in some cases, causing their operation to time-out). That balance is up to you to configure, hence why this is an advanced feature.
|
||||
|
||||
Once a solution to this challenge has been provided, by refreshing the page with the solution in the query parameter, GoToSocial will verify this solution and on success will return the expected profile / status page with a cookie that provides challenge-less access to the instance for up-to the next hour.
|
||||
|
||||
|
|
|
|||
|
|
@ -1307,10 +1307,9 @@ advanced-header-filter-mode: ""
|
|||
advanced-scraper-deterrence-enabled: false
|
||||
|
||||
# Uint. Allows tweaking the difficulty of the proof-of-work algorithm
|
||||
# used in the scraper deterrence. This determines how many leading '0'
|
||||
# characters are required to be generated in each solution. Higher
|
||||
# values will on-average take longer to find solutions for, and the
|
||||
# inverse is also true.
|
||||
# used in the scraper deterrence. This determines roughly how many hash
|
||||
# encode rounds we require the client to complete to find a solution.
|
||||
# Higher values will take longer to find solutions for, and vice-versa.
|
||||
#
|
||||
# The downside is that if your deterrence takes too long to solve,
|
||||
# it may deter some users from viewing your web status / profile page.
|
||||
|
|
@ -1321,6 +1320,6 @@ advanced-scraper-deterrence-enabled: false
|
|||
# For more details please check the documentation at:
|
||||
# https://docs.gotosocial.org/en/latest/advanced/scraper_deterrence
|
||||
#
|
||||
# Examples: [3, 4, 5]
|
||||
# Default: 4
|
||||
advanced-scraper-deterrence-difficulty: 4
|
||||
# Examples: [50000, 100000, 500000]
|
||||
# Default: 100000
|
||||
advanced-scraper-deterrence-difficulty: 100000
|
||||
|
|
|
|||
51
go.mod
51
go.mod
|
|
@ -15,6 +15,7 @@ require (
|
|||
code.superseriousbusiness.org/exif-terminator v0.11.0
|
||||
code.superseriousbusiness.org/httpsig v1.4.0
|
||||
code.superseriousbusiness.org/oauth2/v4 v4.8.0
|
||||
codeberg.org/gruf/go-bitutil v1.1.0
|
||||
codeberg.org/gruf/go-bytesize v1.0.3
|
||||
codeberg.org/gruf/go-byteutil v1.3.0
|
||||
codeberg.org/gruf/go-cache/v3 v3.6.1
|
||||
|
|
@ -74,8 +75,8 @@ require (
|
|||
github.com/uptrace/bun/extra/bunotel v1.2.11
|
||||
github.com/wagslane/go-password-validator v0.3.0
|
||||
github.com/yuin/goldmark v1.7.12
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.60.0
|
||||
go.opentelemetry.io/contrib/instrumentation/runtime v0.60.0
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.61.0
|
||||
go.opentelemetry.io/contrib/instrumentation/runtime v0.61.0
|
||||
go.opentelemetry.io/otel v1.36.0
|
||||
go.opentelemetry.io/otel/metric v1.36.0
|
||||
go.opentelemetry.io/otel/sdk v1.36.0
|
||||
|
|
@ -109,7 +110,7 @@ require (
|
|||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/bytedance/sonic v1.13.2 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
|
|
@ -153,7 +154,7 @@ require (
|
|||
github.com/gorilla/handlers v1.5.2 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/gorilla/sessions v1.4.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
github.com/imdario/mergo v0.3.16 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
|
|
@ -186,9 +187,9 @@ require (
|
|||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_golang v1.22.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.62.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.64.0 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
|
||||
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
|
|
@ -210,30 +211,30 @@ require (
|
|||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
go.mongodb.org/mongo-driver v1.17.3 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.60.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.11.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.11.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.57.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.11.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/log v0.11.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/log v0.11.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.61.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.58.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.12.2 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/log v0.12.2 // indirect
|
||||
go.opentelemetry.io/otel/sdk/log v0.12.2 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.6.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/arch v0.16.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
||||
golang.org/x/mod v0.24.0 // indirect
|
||||
golang.org/x/sync v0.14.0 // indirect
|
||||
golang.org/x/tools v0.33.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect
|
||||
google.golang.org/grpc v1.71.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect
|
||||
google.golang.org/grpc v1.72.1 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
modernc.org/libc v1.65.7 // indirect
|
||||
|
|
|
|||
104
go.sum
generated
104
go.sum
generated
|
|
@ -10,6 +10,8 @@ code.superseriousbusiness.org/httpsig v1.4.0 h1:g9+KQMoTG0oR0II5gYb5pVVdNjbc7Cii
|
|||
code.superseriousbusiness.org/httpsig v1.4.0/go.mod h1:i2AKpj/WbA/o/UTvia9TAREzt0jP1AH3T1Uxjyhdzlw=
|
||||
code.superseriousbusiness.org/oauth2/v4 v4.8.0 h1:4LVXoPJXKgmDfwDegzBQPNpsdleMaL6YmDgFi6UDgEE=
|
||||
code.superseriousbusiness.org/oauth2/v4 v4.8.0/go.mod h1:+RLRBXPkjP/VhIC/46dcZkx3t5IvBSJYOjVCPgeWors=
|
||||
codeberg.org/gruf/go-bitutil v1.1.0 h1:U1Q+A1mtnPk+npqYrlRBc9ar2C5hYiBd17l1Wrp2Bt8=
|
||||
codeberg.org/gruf/go-bitutil v1.1.0/go.mod h1:rGibFevYTQfYKcPv0Df5KpG8n5xC3AfD4d/UgYeoNy0=
|
||||
codeberg.org/gruf/go-bytesize v1.0.3 h1:Tz8tCxhPLeyM5VryuBNjUHgKmLj4Bx9RbPaUSA3qg6g=
|
||||
codeberg.org/gruf/go-bytesize v1.0.3/go.mod h1:n/GU8HzL9f3UNp/mUKyr1qVmTlj7+xacpp0OHfkvLPs=
|
||||
codeberg.org/gruf/go-byteutil v1.3.0 h1:nRqJnCcRQ7xbfU6azw7zOzJrSMDIJHBqX6FL9vEMYmU=
|
||||
|
|
@ -86,8 +88,8 @@ github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1
|
|||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
||||
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
|
||||
github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||
|
|
@ -243,8 +245,8 @@ github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzq
|
|||
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
|
||||
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
||||
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
|
|
@ -350,12 +352,12 @@ github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4
|
|||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
|
||||
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
|
||||
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4=
|
||||
github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b h1:aUNXCGgukb4gtY99imuIeoh8Vr0GSwAlYxPAhqZrpFc=
|
||||
|
|
@ -497,50 +499,52 @@ go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeH
|
|||
go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.60.0 h1:x7sPooQCwSg27SjtQee8GyIIRTQcF4s7eSkac6F2+VA=
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.60.0/go.mod h1:4K5UXgiHxV484efGs42ejD7E2J/sIlepYgdGoPXe7hE=
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.60.0 h1:GuQXpvSXNjpswpweIem84U9BNauqHHi2w1GtNAalvpM=
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.60.0/go.mod h1:CkmxekdHco4d7thFJNPQ7Mby4jMBgZUclnrxT4e+ryk=
|
||||
go.opentelemetry.io/contrib/instrumentation/runtime v0.60.0 h1:0NgN/3SYkqYJ9NBlDfl/2lzVlwos/YQLvi8sUrzJRBE=
|
||||
go.opentelemetry.io/contrib/instrumentation/runtime v0.60.0/go.mod h1:oxpUfhTkhgQaYIjtBt3T3w135dLoxq//qo3WPlPIKkE=
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.61.0 h1:RyrtJzu5MAmIcbRrwg75b+w3RlZCP0vJByDVzcpAe3M=
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.61.0/go.mod h1:tirr4p9NXbzjlbruiRGp53IzlYrDk5CO2fdHj0sSSaY=
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.61.0 h1:XfzKtKSrbtYk9TNCF8dkO0Y9M7IOfb4idCwBOTwGBiI=
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.61.0/go.mod h1:N6otC+qXTD5bAnbK2O1f/1SXq3cX+3KYSWrkBUqG0cw=
|
||||
go.opentelemetry.io/contrib/instrumentation/runtime v0.61.0 h1:oIZsTHd0YcrvvUCN2AaQqyOcd685NQ+rFmrajveCIhA=
|
||||
go.opentelemetry.io/contrib/instrumentation/runtime v0.61.0/go.mod h1:X4KSPIvxnY/G5c9UOGXtFoL91t1gmlHpDQzeK5Zc/Bw=
|
||||
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
|
||||
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.11.0 h1:HMUytBT3uGhPKYY/u/G5MR9itrlSO2SMOsSD3Tk3k7A=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.11.0/go.mod h1:hdDXsiNLmdW/9BF2jQpnHHlhFajpWCEYfM6e5m2OAZg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.11.0 h1:C/Wi2F8wEmbxJ9Kuzw/nhP+Z9XaHYMkyDmXy6yR2cjw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.11.0/go.mod h1:0Lr9vmGKzadCTgsiBydxr6GEZ8SsZ7Ks53LzjWG5Ar4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 h1:QcFwRrZLc82r8wODjvyCbP7Ifp3UANaBSmhDSFjnqSc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0/go.mod h1:CXIWhUomyWBG/oY2/r/kLp6K/cmx9e/7DLpBuuGdLCA=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.35.0 h1:0NIXxOCFx+SKbhCVxwl3ETG8ClLPAa0KuKV6p3yhxP8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.35.0/go.mod h1:ChZSJbbfbl/DcRZNc9Gqh6DYGlfjw4PvO1pEOZH1ZsE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.57.0 h1:AHh/lAP1BHrY5gBwk8ncc25FXWm/gmmY3BX258z5nuk=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.57.0/go.mod h1:QpFWz1QxqevfjwzYdbMb4Y1NnlJvqSGwyuU0B4iuc9c=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.11.0 h1:k6KdfZk72tVW/QVZf60xlDziDvYAePj5QHwoQvrB2m8=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.11.0/go.mod h1:5Y3ZJLqzi/x/kYtrSrPSx7TFI/SGsL7q2kME027tH6I=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.35.0 h1:PB3Zrjs1sG1GBX51SXyTSoOTqcDglmsk7nT6tkKPb/k=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.35.0/go.mod h1:U2R3XyVPzn0WX7wOIypPuptulsMcPDPs/oiSVOMVnHY=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0 h1:T0Ec2E+3YZf5bgTNQVet8iTDW7oIk03tXHq+wkwIDnE=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0/go.mod h1:30v2gqH+vYGJsesLWFov8u47EpYTcIQcBjKpI6pJThg=
|
||||
go.opentelemetry.io/otel/log v0.11.0 h1:c24Hrlk5WJ8JWcwbQxdBqxZdOK7PcP/LFtOtwpDTe3Y=
|
||||
go.opentelemetry.io/otel/log v0.11.0/go.mod h1:U/sxQ83FPmT29trrifhQg+Zj2lo1/IPN1PF6RTFqdwc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 h1:06ZeJRe5BnYXceSM9Vya83XXVaNGe3H1QqsvqRANQq8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2/go.mod h1:DvPtKE63knkDVP88qpatBj81JxN+w1bqfVbsbCbj1WY=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2 h1:tPLwQlXbJ8NSOfZc4OkgU5h2A38M4c9kfHSVc4PFQGs=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2/go.mod h1:QTnxBwT/1rBIgAG1goq6xMydfYOBKU6KTiYF4fp5zL8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0 h1:zwdo1gS2eH26Rg+CoqVQpEK1h8gvt5qyU5Kk5Bixvow=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0/go.mod h1:rUKCPscaRWWcqGT6HnEmYrK+YNe5+Sw64xgQTOJ5b30=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.36.0 h1:gAU726w9J8fwr4qRDqu1GYMNNs4gXrU+Pv20/N1UpB4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.36.0/go.mod h1:RboSDkp7N292rgu+T0MgVt2qgFGu6qa1RpZDOtpL76w=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 h1:nRVXXvf78e00EwY6Wp0YII8ww2JVWshZ20HfTlE11AM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0/go.mod h1:r49hO7CgrxY9Voaj3Xe8pANWtr0Oq916d0XAmOoCZAQ=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.58.0 h1:CJAxWKFIqdBennqxJyOgnt5LqkeFRT+Mz3Yjz3hL+h8=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.58.0/go.mod h1:7qo/4CLI+zYSNbv0GMNquzuss2FVZo3OYrGh96n4HNc=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.12.2 h1:12vMqzLLNZtXuXbJhSENRg+Vvx+ynNilV8twBLBsXMY=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.12.2/go.mod h1:ZccPZoPOoq8x3Trik/fCsba7DEYDUnN6yX79pgp2BUQ=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0 h1:G8Xec/SgZQricwWBJF/mHZc7A02YHedfFDENwJEdRA0=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0/go.mod h1:PD57idA/AiFD5aqoxGxCvT/ILJPeHy3MjqU/NS7KogY=
|
||||
go.opentelemetry.io/otel/log v0.12.2 h1:yob9JVHn2ZY24byZeaXpTVoPS6l+UrrxmxmPKohXTwc=
|
||||
go.opentelemetry.io/otel/log v0.12.2/go.mod h1:ShIItIxSYxufUMt+1H5a2wbckGli3/iCfuEbVZi/98E=
|
||||
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
|
||||
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
|
||||
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
|
||||
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
|
||||
go.opentelemetry.io/otel/sdk/log v0.11.0 h1:7bAOpjpGglWhdEzP8z0VXc4jObOiDEwr3IYbhBnjk2c=
|
||||
go.opentelemetry.io/otel/sdk/log v0.11.0/go.mod h1:dndLTxZbwBstZoqsJB3kGsRPkpAgaJrWfQg3lhlHFFY=
|
||||
go.opentelemetry.io/otel/sdk/log v0.12.2 h1:yNoETvTByVKi7wHvYS6HMcZrN5hFLD7I++1xIZ/k6W0=
|
||||
go.opentelemetry.io/otel/sdk/log v0.12.2/go.mod h1:DcpdmUXHJgSqN/dh+XMWa7Vf89u9ap0/AAk/XGLnEzY=
|
||||
go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc h1:uqxdywfHqqCl6LmZzI3pUnXT1RGFYyUgxj0AkWPFxi0=
|
||||
go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc/go.mod h1:TY/N/FT7dmFrP/r5ym3g0yysP1DefqGpAZr4f82P0dE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
|
||||
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
|
||||
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
|
||||
go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI=
|
||||
go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc=
|
||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
|
|
@ -648,12 +652,12 @@ golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxb
|
|||
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
||||
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
|
||||
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
|
||||
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
|
||||
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
|
|
|||
|
|
@ -280,6 +280,6 @@ type ThrottlingConfig struct {
|
|||
}
|
||||
|
||||
type ScraperDeterrenceConfig struct {
|
||||
Enabled bool `name:"enabled" usage:"Enable proof-of-work based scraper deterrence on profile / status pages"`
|
||||
Difficulty uint8 `name:"difficulty" usage:"The proof-of-work difficulty, which determines how many leading zeros to try solve in hash solutions."`
|
||||
Enabled bool `name:"enabled" usage:"Enable proof-of-work based scraper deterrence on profile / status pages"`
|
||||
Difficulty uint32 `name:"difficulty" usage:"The proof-of-work difficulty, which determines roughly how many hash-encode rounds required of each client."`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ var Defaults = Configuration{
|
|||
|
||||
ScraperDeterrence: ScraperDeterrenceConfig{
|
||||
Enabled: false,
|
||||
Difficulty: 4,
|
||||
Difficulty: 100000,
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ func (cfg *Configuration) RegisterFlags(flags *pflag.FlagSet) {
|
|||
flags.Int("advanced-throttling-multiplier", cfg.Advanced.Throttling.Multiplier, "Multiplier to use per cpu for http request throttling. 0 or less turns throttling off.")
|
||||
flags.Duration("advanced-throttling-retry-after", cfg.Advanced.Throttling.RetryAfter, "Retry-After duration response to send for throttled requests.")
|
||||
flags.Bool("advanced-scraper-deterrence-enabled", cfg.Advanced.ScraperDeterrence.Enabled, "Enable proof-of-work based scraper deterrence on profile / status pages")
|
||||
flags.Uint8("advanced-scraper-deterrence-difficulty", cfg.Advanced.ScraperDeterrence.Difficulty, "The proof-of-work difficulty, which determines how many leading zeros to try solve in hash solutions.")
|
||||
flags.Uint32("advanced-scraper-deterrence-difficulty", cfg.Advanced.ScraperDeterrence.Difficulty, "The proof-of-work difficulty, which determines how many leading zeros to try solve in hash solutions.")
|
||||
flags.StringSlice("http-client-allow-ips", cfg.HTTPClient.AllowIPs, "")
|
||||
flags.StringSlice("http-client-block-ips", cfg.HTTPClient.BlockIPs, "")
|
||||
flags.Duration("http-client-timeout", cfg.HTTPClient.Timeout, "")
|
||||
|
|
@ -1356,9 +1356,9 @@ func (cfg *Configuration) UnmarshalMap(cfgmap map[string]any) error {
|
|||
|
||||
if ival, ok := cfgmap["advanced-scraper-deterrence-difficulty"]; ok {
|
||||
var err error
|
||||
cfg.Advanced.ScraperDeterrence.Difficulty, err = cast.ToUint8E(ival)
|
||||
cfg.Advanced.ScraperDeterrence.Difficulty, err = cast.ToUint32E(ival)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error casting %#v -> uint8 for 'advanced-scraper-deterrence-difficulty': %w", ival, err)
|
||||
return fmt.Errorf("error casting %#v -> uint32 for 'advanced-scraper-deterrence-difficulty': %w", ival, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4799,7 +4799,7 @@ func AdvancedScraperDeterrenceDifficultyFlag() string {
|
|||
}
|
||||
|
||||
// GetAdvancedScraperDeterrenceDifficulty safely fetches the Configuration value for state's 'Advanced.ScraperDeterrence.Difficulty' field
|
||||
func (st *ConfigState) GetAdvancedScraperDeterrenceDifficulty() (v uint8) {
|
||||
func (st *ConfigState) GetAdvancedScraperDeterrenceDifficulty() (v uint32) {
|
||||
st.mutex.RLock()
|
||||
v = st.config.Advanced.ScraperDeterrence.Difficulty
|
||||
st.mutex.RUnlock()
|
||||
|
|
@ -4807,7 +4807,7 @@ func (st *ConfigState) GetAdvancedScraperDeterrenceDifficulty() (v uint8) {
|
|||
}
|
||||
|
||||
// SetAdvancedScraperDeterrenceDifficulty safely sets the Configuration value for state's 'Advanced.ScraperDeterrence.Difficulty' field
|
||||
func (st *ConfigState) SetAdvancedScraperDeterrenceDifficulty(v uint8) {
|
||||
func (st *ConfigState) SetAdvancedScraperDeterrenceDifficulty(v uint32) {
|
||||
st.mutex.Lock()
|
||||
defer st.mutex.Unlock()
|
||||
st.config.Advanced.ScraperDeterrence.Difficulty = v
|
||||
|
|
@ -4815,12 +4815,12 @@ func (st *ConfigState) SetAdvancedScraperDeterrenceDifficulty(v uint8) {
|
|||
}
|
||||
|
||||
// GetAdvancedScraperDeterrenceDifficulty safely fetches the value for global configuration 'Advanced.ScraperDeterrence.Difficulty' field
|
||||
func GetAdvancedScraperDeterrenceDifficulty() uint8 {
|
||||
func GetAdvancedScraperDeterrenceDifficulty() uint32 {
|
||||
return global.GetAdvancedScraperDeterrenceDifficulty()
|
||||
}
|
||||
|
||||
// SetAdvancedScraperDeterrenceDifficulty safely sets the value for global configuration 'Advanced.ScraperDeterrence.Difficulty' field
|
||||
func SetAdvancedScraperDeterrenceDifficulty(v uint8) {
|
||||
func SetAdvancedScraperDeterrenceDifficulty(v uint32) {
|
||||
global.SetAdvancedScraperDeterrenceDifficulty(v)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -336,7 +336,6 @@ func bunDB(sqldb *sql.DB, dialect func() schema.Dialect) *bun.DB {
|
|||
>smodel.ConversationToStatus{},
|
||||
>smodel.StatusToEmoji{},
|
||||
>smodel.StatusToTag{},
|
||||
>smodel.ThreadToStatus{},
|
||||
} {
|
||||
db.RegisterModel(t)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import (
|
|||
"context"
|
||||
"strings"
|
||||
|
||||
gtsmodel "code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
|
||||
gtsmodel "code.superseriousbusiness.org/gotosocial/internal/db/bundb/migrations/20231016113235_mute_status_thread"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/log"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/dialect"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package gtsmodel
|
||||
|
||||
// Thread represents one thread of statuses.
|
||||
// TODO: add more fields here if necessary.
|
||||
type Thread struct {
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
StatusIDs []string `bun:"-"` // ids of statuses belonging to this thread (order not guaranteed)
|
||||
}
|
||||
|
||||
// ThreadToStatus is an intermediate struct to facilitate the
|
||||
// many2many relationship between a thread and one or more statuses.
|
||||
type ThreadToStatus struct {
|
||||
ThreadID string `bun:"type:CHAR(26),unique:statusthread,nullzero,notnull"`
|
||||
StatusID string `bun:"type:CHAR(26),unique:statusthread,nullzero,notnull"`
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package gtsmodel
|
||||
|
||||
import "time"
|
||||
|
||||
// ThreadMute represents an account-level mute of a thread of statuses.
|
||||
type ThreadMute struct {
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
ThreadID string `bun:"type:CHAR(26),nullzero,notnull,unique:thread_mute_thread_id_account_id"` // ID of the muted thread
|
||||
AccountID string `bun:"type:CHAR(26),nullzero,notnull,unique:thread_mute_thread_id_account_id"` // Account ID of the creator of this mute
|
||||
}
|
||||
|
|
@ -0,0 +1,584 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"code.superseriousbusiness.org/gotosocial/internal/db"
|
||||
newmodel "code.superseriousbusiness.org/gotosocial/internal/db/bundb/migrations/20250415111056_thread_all_statuses/new"
|
||||
oldmodel "code.superseriousbusiness.org/gotosocial/internal/db/bundb/migrations/20250415111056_thread_all_statuses/old"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/gtserror"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/id"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/log"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func init() {
|
||||
up := func(ctx context.Context, db *bun.DB) error {
|
||||
newType := reflect.TypeOf(&newmodel.Status{})
|
||||
|
||||
// Get the new column definition with not-null thread_id.
|
||||
newColDef, err := getBunColumnDef(db, newType, "ThreadID")
|
||||
if err != nil {
|
||||
return gtserror.Newf("error getting bun column def: %w", err)
|
||||
}
|
||||
|
||||
// Update column def to use '${name}_new'.
|
||||
newColDef = strings.Replace(newColDef,
|
||||
"thread_id", "thread_id_new", 1)
|
||||
|
||||
var sr statusRethreader
|
||||
var count int
|
||||
var maxID string
|
||||
var statuses []*oldmodel.Status
|
||||
|
||||
// Get a total count of all statuses before migration.
|
||||
total, err := db.NewSelect().Table("statuses").Count(ctx)
|
||||
if err != nil {
|
||||
return gtserror.Newf("error getting status table count: %w", err)
|
||||
}
|
||||
|
||||
// Start at largest
|
||||
// possible ULID value.
|
||||
maxID = id.Highest
|
||||
|
||||
log.Warn(ctx, "rethreading top-level statuses, this will take a *long* time")
|
||||
for /* TOP LEVEL STATUS LOOP */ {
|
||||
|
||||
// Reset slice.
|
||||
clear(statuses)
|
||||
statuses = statuses[:0]
|
||||
|
||||
// Select top-level statuses.
|
||||
if err := db.NewSelect().
|
||||
Model(&statuses).
|
||||
Column("id", "thread_id").
|
||||
|
||||
// We specifically use in_reply_to_account_id instead of in_reply_to_id as
|
||||
// they should both be set / unset in unison, but we specifically have an
|
||||
// index on in_reply_to_account_id with ID ordering, unlike in_reply_to_id.
|
||||
Where("? IS NULL", bun.Ident("in_reply_to_account_id")).
|
||||
Where("? < ?", bun.Ident("id"), maxID).
|
||||
OrderExpr("? DESC", bun.Ident("id")).
|
||||
Limit(5000).
|
||||
Scan(ctx); err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return gtserror.Newf("error selecting top level statuses: %w", err)
|
||||
}
|
||||
|
||||
// Reached end of block.
|
||||
if len(statuses) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// Set next maxID value from statuses.
|
||||
maxID = statuses[len(statuses)-1].ID
|
||||
|
||||
// Rethread each selected batch of top-level statuses in a transaction.
|
||||
if err := db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||
|
||||
// Rethread each top-level status.
|
||||
for _, status := range statuses {
|
||||
n, err := sr.rethreadStatus(ctx, tx, status)
|
||||
if err != nil {
|
||||
return gtserror.Newf("error rethreading status %s: %w", status.URI, err)
|
||||
}
|
||||
count += n
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof(ctx, "[approx %d of %d] rethreading statuses (top-level)", count, total)
|
||||
}
|
||||
|
||||
// Attempt to merge any sqlite write-ahead-log.
|
||||
if err := doWALCheckpoint(ctx, db); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Warn(ctx, "rethreading straggler statuses, this will take a *long* time")
|
||||
for /* STRAGGLER STATUS LOOP */ {
|
||||
|
||||
// Reset slice.
|
||||
clear(statuses)
|
||||
statuses = statuses[:0]
|
||||
|
||||
// Select straggler statuses.
|
||||
if err := db.NewSelect().
|
||||
Model(&statuses).
|
||||
Column("id", "in_reply_to_id", "thread_id").
|
||||
Where("? IS NULL", bun.Ident("thread_id")).
|
||||
|
||||
// We select in smaller batches for this part
|
||||
// of the migration as there is a chance that
|
||||
// we may be fetching statuses that might be
|
||||
// part of the same thread, i.e. one call to
|
||||
// rethreadStatus() may effect other statuses
|
||||
// later in the slice.
|
||||
Limit(1000).
|
||||
Scan(ctx); err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return gtserror.Newf("error selecting straggler statuses: %w", err)
|
||||
}
|
||||
|
||||
// Reached end of block.
|
||||
if len(statuses) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// Rethread each selected batch of straggler statuses in a transaction.
|
||||
if err := db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||
|
||||
// Rethread each top-level status.
|
||||
for _, status := range statuses {
|
||||
n, err := sr.rethreadStatus(ctx, tx, status)
|
||||
if err != nil {
|
||||
return gtserror.Newf("error rethreading status %s: %w", status.URI, err)
|
||||
}
|
||||
count += n
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof(ctx, "[approx %d of %d] rethreading statuses (stragglers)", count, total)
|
||||
}
|
||||
|
||||
// Attempt to merge any sqlite write-ahead-log.
|
||||
if err := doWALCheckpoint(ctx, db); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info(ctx, "dropping old thread_to_statuses table")
|
||||
if _, err := db.NewDropTable().
|
||||
Table("thread_to_statuses").
|
||||
Exec(ctx); err != nil {
|
||||
return gtserror.Newf("error dropping old thread_to_statuses table: %w", err)
|
||||
}
|
||||
|
||||
log.Info(ctx, "creating new statuses thread_id column")
|
||||
if _, err := db.NewAddColumn().
|
||||
Table("statuses").
|
||||
ColumnExpr(newColDef).
|
||||
Exec(ctx); err != nil {
|
||||
return gtserror.Newf("error adding new thread_id column: %w", err)
|
||||
}
|
||||
|
||||
log.Info(ctx, "setting thread_id_new = thread_id (this may take a while...)")
|
||||
if err := db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||
return batchUpdateByID(ctx, tx,
|
||||
"statuses", // table
|
||||
"id", // batchByCol
|
||||
"UPDATE ? SET ? = ?", // updateQuery
|
||||
[]any{bun.Ident("statuses"),
|
||||
bun.Ident("thread_id_new"),
|
||||
bun.Ident("thread_id")},
|
||||
)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Attempt to merge any sqlite write-ahead-log.
|
||||
if err := doWALCheckpoint(ctx, db); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info(ctx, "dropping old statuses thread_id index")
|
||||
if _, err := db.NewDropIndex().
|
||||
Index("statuses_thread_id_idx").
|
||||
Exec(ctx); err != nil {
|
||||
return gtserror.Newf("error dropping old thread_id index: %w", err)
|
||||
}
|
||||
|
||||
log.Info(ctx, "dropping old statuses thread_id column")
|
||||
if _, err := db.NewDropColumn().
|
||||
Table("statuses").
|
||||
Column("thread_id").
|
||||
Exec(ctx); err != nil {
|
||||
return gtserror.Newf("error dropping old thread_id column: %w", err)
|
||||
}
|
||||
|
||||
log.Info(ctx, "renaming thread_id_new to thread_id")
|
||||
if _, err := db.NewRaw(
|
||||
"ALTER TABLE ? RENAME COLUMN ? TO ?",
|
||||
bun.Ident("statuses"),
|
||||
bun.Ident("thread_id_new"),
|
||||
bun.Ident("thread_id"),
|
||||
).Exec(ctx); err != nil {
|
||||
return gtserror.Newf("error renaming new column: %w", err)
|
||||
}
|
||||
|
||||
log.Info(ctx, "creating new statuses thread_id index")
|
||||
if _, err := db.NewCreateIndex().
|
||||
Table("statuses").
|
||||
Index("statuses_thread_id_idx").
|
||||
Column("thread_id").
|
||||
Exec(ctx); err != nil {
|
||||
return gtserror.Newf("error creating new thread_id index: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
down := func(ctx context.Context, db *bun.DB) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := Migrations.Register(up, down); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
type statusRethreader struct {
|
||||
// the unique status and thread IDs
|
||||
// of all models passed to append().
|
||||
// these are later used to update all
|
||||
// statuses to a single thread ID, and
|
||||
// update all thread related models to
|
||||
// use the new updated thread ID.
|
||||
statusIDs []string
|
||||
threadIDs []string
|
||||
|
||||
// stores the unseen IDs of status
|
||||
// InReplyTos newly tracked in append(),
|
||||
// which is then used for a SELECT query
|
||||
// in getParents(), then promptly reset.
|
||||
inReplyToIDs []string
|
||||
|
||||
// statuses simply provides a reusable
|
||||
// slice of status models for selects.
|
||||
// its contents are ephemeral.
|
||||
statuses []*oldmodel.Status
|
||||
|
||||
// seenIDs tracks the unique status and
|
||||
// thread IDs we have seen, ensuring we
|
||||
// don't append duplicates to statusIDs
|
||||
// or threadIDs slices. also helps prevent
|
||||
// adding duplicate parents to inReplyToIDs.
|
||||
seenIDs map[string]struct{}
|
||||
|
||||
// allThreaded tracks whether every status
|
||||
// passed to append() has a thread ID set.
|
||||
// together with len(threadIDs) this can
|
||||
// determine if already threaded correctly.
|
||||
allThreaded bool
|
||||
}
|
||||
|
||||
// rethreadStatus is the main logic handler for statusRethreader{}. this is what gets called from the migration
|
||||
// in order to trigger a status rethreading operation for the given status, returning total number rethreaded.
|
||||
func (sr *statusRethreader) rethreadStatus(ctx context.Context, tx bun.Tx, status *oldmodel.Status) (int, error) {
|
||||
|
||||
// Zero slice and
|
||||
// map ptr values.
|
||||
clear(sr.statusIDs)
|
||||
clear(sr.threadIDs)
|
||||
clear(sr.statuses)
|
||||
clear(sr.seenIDs)
|
||||
|
||||
// Reset slices and values for use.
|
||||
sr.statusIDs = sr.statusIDs[:0]
|
||||
sr.threadIDs = sr.threadIDs[:0]
|
||||
sr.statuses = sr.statuses[:0]
|
||||
sr.allThreaded = true
|
||||
|
||||
if sr.seenIDs == nil {
|
||||
// Allocate new hash set for status IDs.
|
||||
sr.seenIDs = make(map[string]struct{})
|
||||
}
|
||||
|
||||
// Ensure the passed status
|
||||
// has up-to-date information.
|
||||
// This may have changed from
|
||||
// the initial batch selection
|
||||
// to the rethreadStatus() call.
|
||||
if err := tx.NewSelect().
|
||||
Model(status).
|
||||
Column("in_reply_to_id", "thread_id").
|
||||
Where("? = ?", bun.Ident("id"), status.ID).
|
||||
Scan(ctx); err != nil {
|
||||
return 0, gtserror.Newf("error selecting status: %w", err)
|
||||
}
|
||||
|
||||
// status and thread ID cursor
|
||||
// index values. these are used
|
||||
// to keep track of newly loaded
|
||||
// status / thread IDs between
|
||||
// loop iterations.
|
||||
var statusIdx int
|
||||
var threadIdx int
|
||||
|
||||
// Append given status as
|
||||
// first to our ID slices.
|
||||
sr.append(status)
|
||||
|
||||
for {
|
||||
// Fetch parents for newly seen in_reply_tos since last loop.
|
||||
if err := sr.getParents(ctx, tx); err != nil {
|
||||
return 0, gtserror.Newf("error getting parents: %w", err)
|
||||
}
|
||||
|
||||
// Fetch children for newly seen statuses since last loop.
|
||||
if err := sr.getChildren(ctx, tx, statusIdx); err != nil {
|
||||
return 0, gtserror.Newf("error getting children: %w", err)
|
||||
}
|
||||
|
||||
// Check for newly picked-up threads
|
||||
// to find stragglers for below. Else
|
||||
// we've reached end of what we can do.
|
||||
if threadIdx >= len(sr.threadIDs) {
|
||||
break
|
||||
}
|
||||
|
||||
// Update status IDs cursor.
|
||||
statusIdx = len(sr.statusIDs)
|
||||
|
||||
// Fetch any stragglers for newly seen threads since last loop.
|
||||
if err := sr.getStragglers(ctx, tx, threadIdx); err != nil {
|
||||
return 0, gtserror.Newf("error getting stragglers: %w", err)
|
||||
}
|
||||
|
||||
// Check for newly picked-up straggling statuses / replies to
|
||||
// find parents / children for. Else we've done all we can do.
|
||||
if statusIdx >= len(sr.statusIDs) && len(sr.inReplyToIDs) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// Update thread IDs cursor.
|
||||
threadIdx = len(sr.threadIDs)
|
||||
}
|
||||
|
||||
// Total number of
|
||||
// statuses threaded.
|
||||
total := len(sr.statusIDs)
|
||||
|
||||
// Check for the case where the entire
|
||||
// batch of statuses is already correctly
|
||||
// threaded. Then we have nothing to do!
|
||||
if sr.allThreaded && len(sr.threadIDs) == 1 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Sort all of the threads and
|
||||
// status IDs by age; old -> new.
|
||||
slices.Sort(sr.threadIDs)
|
||||
slices.Sort(sr.statusIDs)
|
||||
|
||||
var threadID string
|
||||
|
||||
if len(sr.threadIDs) > 0 {
|
||||
// Regardless of whether there ended up being
|
||||
// multiple threads, we take the oldest value
|
||||
// thread ID to use for entire batch of them.
|
||||
threadID = sr.threadIDs[0]
|
||||
sr.threadIDs = sr.threadIDs[1:]
|
||||
}
|
||||
|
||||
if threadID == "" {
|
||||
// None of the previous parents were threaded, we instead
|
||||
// generate new thread with ID based on oldest creation time.
|
||||
createdAt, err := id.TimeFromULID(sr.statusIDs[0])
|
||||
if err != nil {
|
||||
return 0, gtserror.Newf("error parsing status ulid: %w", err)
|
||||
}
|
||||
|
||||
// Generate thread ID from parsed time.
|
||||
threadID = id.NewULIDFromTime(createdAt)
|
||||
|
||||
// We need to create a
|
||||
// new thread table entry.
|
||||
if _, err = tx.NewInsert().
|
||||
Model(&newmodel.Thread{ID: threadID}).
|
||||
Exec(ctx); err != nil {
|
||||
return 0, gtserror.Newf("error creating new thread: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Update all the statuses to
|
||||
// use determined thread_id.
|
||||
if _, err := tx.NewUpdate().
|
||||
Table("statuses").
|
||||
Where("? IN (?)", bun.Ident("id"), bun.In(sr.statusIDs)).
|
||||
Set("? = ?", bun.Ident("thread_id"), threadID).
|
||||
Exec(ctx); err != nil {
|
||||
return 0, gtserror.Newf("error updating status thread ids: %w", err)
|
||||
}
|
||||
|
||||
if len(sr.threadIDs) > 0 {
|
||||
// Update any existing thread
|
||||
// mutes to use latest thread_id.
|
||||
if _, err := tx.NewUpdate().
|
||||
Table("thread_mutes").
|
||||
Where("? IN (?)", bun.Ident("thread_id"), bun.In(sr.threadIDs)).
|
||||
Set("? = ?", bun.Ident("thread_id"), threadID).
|
||||
Exec(ctx); err != nil {
|
||||
return 0, gtserror.Newf("error updating mute thread ids: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return total, nil
|
||||
}
|
||||
|
||||
// append will append the given status to the internal tracking of statusRethreader{} for
|
||||
// potential future operations, checking for uniqueness. it tracks the inReplyToID value
|
||||
// for the next call to getParents(), it tracks the status ID for list of statuses that
|
||||
// need updating, the thread ID for the list of thread links and mutes that need updating,
|
||||
// and whether all the statuses all have a provided thread ID (i.e. allThreaded).
|
||||
func (sr *statusRethreader) append(status *oldmodel.Status) {
|
||||
|
||||
// Check if status already seen before.
|
||||
if _, ok := sr.seenIDs[status.ID]; ok {
|
||||
return
|
||||
}
|
||||
|
||||
if status.InReplyToID != "" {
|
||||
// Status has a parent, add any unique parent ID
|
||||
// to list of reply IDs that need to be queried.
|
||||
if _, ok := sr.seenIDs[status.InReplyToID]; ok {
|
||||
sr.inReplyToIDs = append(sr.inReplyToIDs, status.InReplyToID)
|
||||
}
|
||||
}
|
||||
|
||||
// Add status' ID to list of seen status IDs.
|
||||
sr.statusIDs = append(sr.statusIDs, status.ID)
|
||||
|
||||
if status.ThreadID != "" {
|
||||
// Status was threaded, add any unique thread
|
||||
// ID to our list of known status thread IDs.
|
||||
if _, ok := sr.seenIDs[status.ThreadID]; !ok {
|
||||
sr.threadIDs = append(sr.threadIDs, status.ThreadID)
|
||||
}
|
||||
} else {
|
||||
// Status was not threaded,
|
||||
// we now know not all statuses
|
||||
// found were threaded.
|
||||
sr.allThreaded = false
|
||||
}
|
||||
|
||||
// Add status ID to map of seen IDs.
|
||||
sr.seenIDs[status.ID] = struct{}{}
|
||||
}
|
||||
|
||||
func (sr *statusRethreader) getParents(ctx context.Context, tx bun.Tx) error {
|
||||
var parent oldmodel.Status
|
||||
|
||||
// Iteratively query parent for each stored
|
||||
// reply ID. Note this is safe to do as slice
|
||||
// loop since 'seenIDs' prevents duplicates.
|
||||
for i := 0; i < len(sr.inReplyToIDs); i++ {
|
||||
|
||||
// Get next status ID.
|
||||
id := sr.statusIDs[i]
|
||||
|
||||
// Select next parent status.
|
||||
if err := tx.NewSelect().
|
||||
Model(&parent).
|
||||
Column("id", "in_reply_to_id", "thread_id").
|
||||
Where("? = ?", bun.Ident("id"), id).
|
||||
Scan(ctx); err != nil && err != db.ErrNoEntries {
|
||||
return err
|
||||
}
|
||||
|
||||
// Parent was missing.
|
||||
if parent.ID == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Add to slices.
|
||||
sr.append(&parent)
|
||||
}
|
||||
|
||||
// Reset reply slice.
|
||||
clear(sr.inReplyToIDs)
|
||||
sr.inReplyToIDs = sr.inReplyToIDs[:0]
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sr *statusRethreader) getChildren(ctx context.Context, tx bun.Tx, idx int) error {
|
||||
// Iteratively query all children for each
|
||||
// of fetched parent statuses. Note this is
|
||||
// safe to do as a slice loop since 'seenIDs'
|
||||
// ensures it only ever contains unique IDs.
|
||||
for i := idx; i < len(sr.statusIDs); i++ {
|
||||
|
||||
// Get next status ID.
|
||||
id := sr.statusIDs[i]
|
||||
|
||||
// Reset child slice.
|
||||
clear(sr.statuses)
|
||||
sr.statuses = sr.statuses[:0]
|
||||
|
||||
// Select children of ID.
|
||||
if err := tx.NewSelect().
|
||||
Model(&sr.statuses).
|
||||
Column("id", "thread_id").
|
||||
Where("? = ?", bun.Ident("in_reply_to_id"), id).
|
||||
Scan(ctx); err != nil && err != db.ErrNoEntries {
|
||||
return err
|
||||
}
|
||||
|
||||
// Append child status IDs to slices.
|
||||
for _, child := range sr.statuses {
|
||||
sr.append(child)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sr *statusRethreader) getStragglers(ctx context.Context, tx bun.Tx, idx int) error {
|
||||
// Check for threads to query.
|
||||
if idx >= len(sr.threadIDs) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reset status slice.
|
||||
clear(sr.statuses)
|
||||
sr.statuses = sr.statuses[:0]
|
||||
|
||||
// Select stragglers that
|
||||
// also have thread IDs.
|
||||
if err := tx.NewSelect().
|
||||
Model(&sr.statuses).
|
||||
Column("id", "thread_id", "in_reply_to_id").
|
||||
Where("? IN (?) AND ? NOT IN (?)",
|
||||
bun.Ident("thread_id"),
|
||||
bun.In(sr.threadIDs[idx:]),
|
||||
bun.Ident("id"),
|
||||
bun.In(sr.statusIDs),
|
||||
).
|
||||
Scan(ctx); err != nil && err != db.ErrNoEntries {
|
||||
return err
|
||||
}
|
||||
|
||||
// Append status IDs to slices.
|
||||
for _, status := range sr.statuses {
|
||||
sr.append(status)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package gtsmodel
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Status represents a user-created 'post' or 'status' in the database, either remote or local
|
||||
type Status struct {
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
EditedAt time.Time `bun:"type:timestamptz,nullzero"` // when this status was last edited (if set)
|
||||
FetchedAt time.Time `bun:"type:timestamptz,nullzero"` // when was item (remote) last fetched.
|
||||
PinnedAt time.Time `bun:"type:timestamptz,nullzero"` // Status was pinned by owning account at this time.
|
||||
URI string `bun:",unique,nullzero,notnull"` // activitypub URI of this status
|
||||
URL string `bun:",nullzero"` // web url for viewing this status
|
||||
Content string `bun:""` // Content HTML for this status.
|
||||
AttachmentIDs []string `bun:"attachments,array"` // Database IDs of any media attachments associated with this status
|
||||
TagIDs []string `bun:"tags,array"` // Database IDs of any tags used in this status
|
||||
MentionIDs []string `bun:"mentions,array"` // Database IDs of any mentions in this status
|
||||
EmojiIDs []string `bun:"emojis,array"` // Database IDs of any emojis used in this status
|
||||
Local *bool `bun:",nullzero,notnull,default:false"` // is this status from a local account?
|
||||
AccountID string `bun:"type:CHAR(26),nullzero,notnull"` // which account posted this status?
|
||||
AccountURI string `bun:",nullzero,notnull"` // activitypub uri of the owner of this status
|
||||
InReplyToID string `bun:"type:CHAR(26),nullzero"` // id of the status this status replies to
|
||||
InReplyToURI string `bun:",nullzero"` // activitypub uri of the status this status is a reply to
|
||||
InReplyToAccountID string `bun:"type:CHAR(26),nullzero"` // id of the account that this status replies to
|
||||
InReplyTo *Status `bun:"-"` // status corresponding to inReplyToID
|
||||
BoostOfID string `bun:"type:CHAR(26),nullzero"` // id of the status this status is a boost of
|
||||
BoostOfURI string `bun:"-"` // URI of the status this status is a boost of; field not inserted in the db, just for dereferencing purposes.
|
||||
BoostOfAccountID string `bun:"type:CHAR(26),nullzero"` // id of the account that owns the boosted status
|
||||
BoostOf *Status `bun:"-"` // status that corresponds to boostOfID
|
||||
ThreadID string `bun:"type:CHAR(26),nullzero,notnull,default:00000000000000000000000000"` // id of the thread to which this status belongs
|
||||
EditIDs []string `bun:"edits,array"` //
|
||||
PollID string `bun:"type:CHAR(26),nullzero"` //
|
||||
ContentWarning string `bun:",nullzero"` // Content warning HTML for this status.
|
||||
ContentWarningText string `bun:""` // Original text of the content warning without formatting
|
||||
Visibility Visibility `bun:",nullzero,notnull"` // visibility entry for this status
|
||||
Sensitive *bool `bun:",nullzero,notnull,default:false"` // mark the status as sensitive?
|
||||
Language string `bun:",nullzero"` // what language is this status written in?
|
||||
CreatedWithApplicationID string `bun:"type:CHAR(26),nullzero"` // Which application was used to create this status?
|
||||
ActivityStreamsType string `bun:",nullzero,notnull"` // What is the activitystreams type of this status? See: https://www.w3.org/TR/activitystreams-vocabulary/#object-types. Will probably almost always be Note but who knows!.
|
||||
Text string `bun:""` // Original text of the status without formatting
|
||||
ContentType StatusContentType `bun:",nullzero"` // Content type used to process the original text of the status
|
||||
Federated *bool `bun:",notnull"` // This status will be federated beyond the local timeline(s)
|
||||
PendingApproval *bool `bun:",nullzero,notnull,default:false"` // If true then status is a reply or boost wrapper that must be Approved by the reply-ee or boost-ee before being fully distributed.
|
||||
PreApproved bool `bun:"-"` // If true, then status is a reply to or boost wrapper of a status on our instance, has permission to do the interaction, and an Accept should be sent out for it immediately. Field not stored in the DB.
|
||||
ApprovedByURI string `bun:",nullzero"` // URI of an Accept Activity that approves the Announce or Create Activity that this status was/will be attached to.
|
||||
}
|
||||
|
||||
// enumType is the type we (at least, should) use
|
||||
// for database enum types. it is the largest size
|
||||
// supported by a PostgreSQL SMALLINT, since an
|
||||
// SQLite SMALLINT is actually variable in size.
|
||||
type enumType int16
|
||||
|
||||
// Visibility represents the
|
||||
// visibility granularity of a status.
|
||||
type Visibility enumType
|
||||
|
||||
const (
|
||||
// VisibilityNone means nobody can see this.
|
||||
// It's only used for web status visibility.
|
||||
VisibilityNone Visibility = 1
|
||||
|
||||
// VisibilityPublic means this status will
|
||||
// be visible to everyone on all timelines.
|
||||
VisibilityPublic Visibility = 2
|
||||
|
||||
// VisibilityUnlocked means this status will be visible to everyone,
|
||||
// but will only show on home timeline to followers, and in lists.
|
||||
VisibilityUnlocked Visibility = 3
|
||||
|
||||
// VisibilityFollowersOnly means this status is viewable to followers only.
|
||||
VisibilityFollowersOnly Visibility = 4
|
||||
|
||||
// VisibilityMutualsOnly means this status
|
||||
// is visible to mutual followers only.
|
||||
VisibilityMutualsOnly Visibility = 5
|
||||
|
||||
// VisibilityDirect means this status is
|
||||
// visible only to mentioned recipients.
|
||||
VisibilityDirect Visibility = 6
|
||||
|
||||
// VisibilityDefault is used when no other setting can be found.
|
||||
VisibilityDefault Visibility = VisibilityUnlocked
|
||||
)
|
||||
|
||||
// String returns a stringified, frontend API compatible form of Visibility.
|
||||
func (v Visibility) String() string {
|
||||
switch v {
|
||||
case VisibilityNone:
|
||||
return "none"
|
||||
case VisibilityPublic:
|
||||
return "public"
|
||||
case VisibilityUnlocked:
|
||||
return "unlocked"
|
||||
case VisibilityFollowersOnly:
|
||||
return "followers_only"
|
||||
case VisibilityMutualsOnly:
|
||||
return "mutuals_only"
|
||||
case VisibilityDirect:
|
||||
return "direct"
|
||||
default:
|
||||
panic("invalid visibility")
|
||||
}
|
||||
}
|
||||
|
||||
// StatusContentType is the content type with which a status's text is
|
||||
// parsed. Can be either plain or markdown. Empty will default to plain.
|
||||
type StatusContentType enumType
|
||||
|
||||
const (
|
||||
StatusContentTypePlain StatusContentType = 1
|
||||
StatusContentTypeMarkdown StatusContentType = 2
|
||||
StatusContentTypeDefault = StatusContentTypePlain
|
||||
)
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package gtsmodel
|
||||
|
||||
// Thread represents one thread of statuses.
|
||||
// TODO: add more fields here if necessary.
|
||||
type Thread struct {
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
}
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package gtsmodel
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Status represents a user-created 'post' or 'status' in the database, either remote or local
|
||||
type Status struct {
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
EditedAt time.Time `bun:"type:timestamptz,nullzero"` // when this status was last edited (if set)
|
||||
FetchedAt time.Time `bun:"type:timestamptz,nullzero"` // when was item (remote) last fetched.
|
||||
PinnedAt time.Time `bun:"type:timestamptz,nullzero"` // Status was pinned by owning account at this time.
|
||||
URI string `bun:",unique,nullzero,notnull"` // activitypub URI of this status
|
||||
URL string `bun:",nullzero"` // web url for viewing this status
|
||||
Content string `bun:""` // Content HTML for this status.
|
||||
AttachmentIDs []string `bun:"attachments,array"` // Database IDs of any media attachments associated with this status
|
||||
TagIDs []string `bun:"tags,array"` // Database IDs of any tags used in this status
|
||||
MentionIDs []string `bun:"mentions,array"` // Database IDs of any mentions in this status
|
||||
EmojiIDs []string `bun:"emojis,array"` // Database IDs of any emojis used in this status
|
||||
Local *bool `bun:",nullzero,notnull,default:false"` // is this status from a local account?
|
||||
AccountID string `bun:"type:CHAR(26),nullzero,notnull"` // which account posted this status?
|
||||
AccountURI string `bun:",nullzero,notnull"` // activitypub uri of the owner of this status
|
||||
InReplyToID string `bun:"type:CHAR(26),nullzero"` // id of the status this status replies to
|
||||
InReplyToURI string `bun:",nullzero"` // activitypub uri of the status this status is a reply to
|
||||
InReplyToAccountID string `bun:"type:CHAR(26),nullzero"` // id of the account that this status replies to
|
||||
BoostOfID string `bun:"type:CHAR(26),nullzero"` // id of the status this status is a boost of
|
||||
BoostOfURI string `bun:"-"` // URI of the status this status is a boost of; field not inserted in the db, just for dereferencing purposes.
|
||||
BoostOfAccountID string `bun:"type:CHAR(26),nullzero"` // id of the account that owns the boosted status
|
||||
ThreadID string `bun:"type:CHAR(26),nullzero"` // id of the thread to which this status belongs; only set for remote statuses if a local account is involved at some point in the thread, otherwise null
|
||||
EditIDs []string `bun:"edits,array"` //
|
||||
PollID string `bun:"type:CHAR(26),nullzero"` //
|
||||
ContentWarning string `bun:",nullzero"` // Content warning HTML for this status.
|
||||
ContentWarningText string `bun:""` // Original text of the content warning without formatting
|
||||
Visibility Visibility `bun:",nullzero,notnull"` // visibility entry for this status
|
||||
Sensitive *bool `bun:",nullzero,notnull,default:false"` // mark the status as sensitive?
|
||||
Language string `bun:",nullzero"` // what language is this status written in?
|
||||
CreatedWithApplicationID string `bun:"type:CHAR(26),nullzero"` // Which application was used to create this status?
|
||||
ActivityStreamsType string `bun:",nullzero,notnull"` // What is the activitystreams type of this status? See: https://www.w3.org/TR/activitystreams-vocabulary/#object-types. Will probably almost always be Note but who knows!.
|
||||
Text string `bun:""` // Original text of the status without formatting
|
||||
ContentType StatusContentType `bun:",nullzero"` // Content type used to process the original text of the status
|
||||
Federated *bool `bun:",notnull"` // This status will be federated beyond the local timeline(s)
|
||||
PendingApproval *bool `bun:",nullzero,notnull,default:false"` // If true then status is a reply or boost wrapper that must be Approved by the reply-ee or boost-ee before being fully distributed.
|
||||
PreApproved bool `bun:"-"` // If true, then status is a reply to or boost wrapper of a status on our instance, has permission to do the interaction, and an Accept should be sent out for it immediately. Field not stored in the DB.
|
||||
ApprovedByURI string `bun:",nullzero"` // URI of an Accept Activity that approves the Announce or Create Activity that this status was/will be attached to.
|
||||
}
|
||||
|
||||
// enumType is the type we (at least, should) use
|
||||
// for database enum types. it is the largest size
|
||||
// supported by a PostgreSQL SMALLINT, since an
|
||||
// SQLite SMALLINT is actually variable in size.
|
||||
type enumType int16
|
||||
|
||||
// Visibility represents the
|
||||
// visibility granularity of a status.
|
||||
type Visibility enumType
|
||||
|
||||
const (
|
||||
// VisibilityNone means nobody can see this.
|
||||
// It's only used for web status visibility.
|
||||
VisibilityNone Visibility = 1
|
||||
|
||||
// VisibilityPublic means this status will
|
||||
// be visible to everyone on all timelines.
|
||||
VisibilityPublic Visibility = 2
|
||||
|
||||
// VisibilityUnlocked means this status will be visible to everyone,
|
||||
// but will only show on home timeline to followers, and in lists.
|
||||
VisibilityUnlocked Visibility = 3
|
||||
|
||||
// VisibilityFollowersOnly means this status is viewable to followers only.
|
||||
VisibilityFollowersOnly Visibility = 4
|
||||
|
||||
// VisibilityMutualsOnly means this status
|
||||
// is visible to mutual followers only.
|
||||
VisibilityMutualsOnly Visibility = 5
|
||||
|
||||
// VisibilityDirect means this status is
|
||||
// visible only to mentioned recipients.
|
||||
VisibilityDirect Visibility = 6
|
||||
|
||||
// VisibilityDefault is used when no other setting can be found.
|
||||
VisibilityDefault Visibility = VisibilityUnlocked
|
||||
)
|
||||
|
||||
// String returns a stringified, frontend API compatible form of Visibility.
|
||||
func (v Visibility) String() string {
|
||||
switch v {
|
||||
case VisibilityNone:
|
||||
return "none"
|
||||
case VisibilityPublic:
|
||||
return "public"
|
||||
case VisibilityUnlocked:
|
||||
return "unlocked"
|
||||
case VisibilityFollowersOnly:
|
||||
return "followers_only"
|
||||
case VisibilityMutualsOnly:
|
||||
return "mutuals_only"
|
||||
case VisibilityDirect:
|
||||
return "direct"
|
||||
default:
|
||||
panic("invalid visibility")
|
||||
}
|
||||
}
|
||||
|
||||
// StatusContentType is the content type with which a status's text is
|
||||
// parsed. Can be either plain or markdown. Empty will default to plain.
|
||||
type StatusContentType enumType
|
||||
|
||||
const (
|
||||
StatusContentTypePlain StatusContentType = 1
|
||||
StatusContentTypeMarkdown StatusContentType = 2
|
||||
StatusContentTypeDefault = StatusContentTypePlain
|
||||
)
|
||||
|
|
@ -26,6 +26,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.superseriousbusiness.org/gotosocial/internal/config"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/gtserror"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/id"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/log"
|
||||
|
|
@ -37,6 +38,112 @@ import (
|
|||
"github.com/uptrace/bun/schema"
|
||||
)
|
||||
|
||||
// doWALCheckpoint attempt to force a WAL file merge on SQLite3,
|
||||
// which can be useful given how much can build-up in the WAL.
|
||||
//
|
||||
// see: https://www.sqlite.org/pragma.html#pragma_wal_checkpoint
|
||||
func doWALCheckpoint(ctx context.Context, db *bun.DB) error {
|
||||
if db.Dialect().Name() == dialect.SQLite && strings.EqualFold(config.GetDbSqliteJournalMode(), "WAL") {
|
||||
_, err := db.ExecContext(ctx, "PRAGMA wal_checkpoint(RESTART);")
|
||||
if err != nil {
|
||||
return gtserror.Newf("error performing wal_checkpoint: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// batchUpdateByID performs the given updateQuery with updateArgs
|
||||
// over the entire given table, batching by the ID of batchByCol.
|
||||
func batchUpdateByID(
|
||||
ctx context.Context,
|
||||
tx bun.Tx,
|
||||
table string,
|
||||
batchByCol string,
|
||||
updateQuery string,
|
||||
updateArgs []any,
|
||||
) error {
|
||||
// Get a count of all in table.
|
||||
total, err := tx.NewSelect().
|
||||
Table(table).
|
||||
Count(ctx)
|
||||
if err != nil {
|
||||
return gtserror.Newf("error selecting total count: %w", err)
|
||||
}
|
||||
|
||||
// Query batch size
|
||||
// in number of rows.
|
||||
const batchsz = 5000
|
||||
|
||||
// Stores highest batch value
|
||||
// used in iterate queries,
|
||||
// starting at highest possible.
|
||||
highest := id.Highest
|
||||
|
||||
// Total updated rows.
|
||||
var updated int
|
||||
|
||||
for {
|
||||
// Limit to batchsz
|
||||
// items at once.
|
||||
batchQ := tx.
|
||||
NewSelect().
|
||||
Table(table).
|
||||
Column(batchByCol).
|
||||
Where("? < ?", bun.Ident(batchByCol), highest).
|
||||
OrderExpr("? DESC", bun.Ident(batchByCol)).
|
||||
Limit(batchsz)
|
||||
|
||||
// Finalize UPDATE to act only on batch.
|
||||
qStr := updateQuery + " WHERE ? IN (?)"
|
||||
args := append(slices.Clone(updateArgs),
|
||||
bun.Ident(batchByCol),
|
||||
batchQ,
|
||||
)
|
||||
|
||||
// Execute the prepared raw query with arguments.
|
||||
res, err := tx.NewRaw(qStr, args...).Exec(ctx)
|
||||
if err != nil {
|
||||
return gtserror.Newf("error updating old column values: %w", err)
|
||||
}
|
||||
|
||||
// Check how many items we updated.
|
||||
thisUpdated, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return gtserror.Newf("error counting affected rows: %w", err)
|
||||
}
|
||||
|
||||
if thisUpdated == 0 {
|
||||
// Nothing updated
|
||||
// means we're done.
|
||||
break
|
||||
}
|
||||
|
||||
// Update the overall count.
|
||||
updated += int(thisUpdated)
|
||||
|
||||
// Log helpful message to admin.
|
||||
log.Infof(ctx, "migrated %d of %d %s (up to %s)",
|
||||
updated, total, table, highest)
|
||||
|
||||
// Get next highest
|
||||
// id for next batch.
|
||||
if err := tx.
|
||||
NewSelect().
|
||||
With("batch_query", batchQ).
|
||||
ColumnExpr("min(?) FROM ?", bun.Ident(batchByCol), bun.Ident("batch_query")).
|
||||
Scan(ctx, &highest); err != nil {
|
||||
return gtserror.Newf("error selecting next highest: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if total != int(updated) {
|
||||
// Return error here in order to rollback the whole transaction.
|
||||
return fmt.Errorf("total=%d does not match updated=%d", total, updated)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// convertEnums performs a transaction that converts
|
||||
// a table's column of our old-style enums (strings) to
|
||||
// more performant and space-saving integer types.
|
||||
|
|
@ -310,7 +417,7 @@ func getModelField(db bun.IDB, rtype reflect.Type, fieldName string) (*schema.Fi
|
|||
}
|
||||
|
||||
// doesColumnExist safely checks whether given column exists on table, handling both SQLite and PostgreSQL appropriately.
|
||||
func doesColumnExist(ctx context.Context, tx bun.Tx, table, col string) (bool, error) {
|
||||
func doesColumnExist(ctx context.Context, tx bun.IDB, table, col string) (bool, error) {
|
||||
var n int
|
||||
var err error
|
||||
switch tx.Dialect().Name() {
|
||||
|
|
|
|||
|
|
@ -21,11 +21,13 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"code.superseriousbusiness.org/gotosocial/internal/db"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/gtscontext"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/gtserror"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/id"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/log"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/state"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/util/xslices"
|
||||
|
|
@ -335,115 +337,284 @@ func (s *statusDB) PutStatus(ctx context.Context, status *gtsmodel.Status) error
|
|||
// as the cache does not attempt a mutex lock until AFTER hook.
|
||||
//
|
||||
return s.db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||
// create links between this status and any emojis it uses
|
||||
for _, i := range status.EmojiIDs {
|
||||
if status.BoostOfID != "" {
|
||||
var threadID string
|
||||
|
||||
// Boost wrappers always inherit thread
|
||||
// of the origin status they're boosting.
|
||||
if err := tx.
|
||||
NewSelect().
|
||||
Table("statuses").
|
||||
Column("thread_id").
|
||||
Where("? = ?", bun.Ident("id"), status.BoostOfID).
|
||||
Scan(ctx, &threadID); err != nil {
|
||||
return gtserror.Newf("error selecting boosted status: %w", err)
|
||||
}
|
||||
|
||||
// Set the selected thread.
|
||||
status.ThreadID = threadID
|
||||
|
||||
// They also require no further
|
||||
// checks! Simply insert status here.
|
||||
return insertStatus(ctx, tx, status)
|
||||
}
|
||||
|
||||
// Gather a list of possible thread IDs
|
||||
// of all the possible related statuses
|
||||
// to this one. If one exists we can use
|
||||
// the end result, and if too many exist
|
||||
// we can fix the status threading.
|
||||
var threadIDs []string
|
||||
|
||||
if status.InReplyToID != "" {
|
||||
var threadID string
|
||||
|
||||
// A stored parent status exists,
|
||||
// select its thread ID to ideally
|
||||
// inherit this for status.
|
||||
if err := tx.
|
||||
NewSelect().
|
||||
Table("statuses").
|
||||
Column("thread_id").
|
||||
Where("? = ?", bun.Ident("id"), status.InReplyToID).
|
||||
Scan(ctx, &threadID); err != nil {
|
||||
return gtserror.Newf("error selecting status parent: %w", err)
|
||||
}
|
||||
|
||||
// Append possible ID to threads slice.
|
||||
threadIDs = append(threadIDs, threadID)
|
||||
|
||||
} else if status.InReplyToURI != "" {
|
||||
var ids []string
|
||||
|
||||
// A parent status exists but is not
|
||||
// yet stored. See if any siblings for
|
||||
// this shared parent exist with their
|
||||
// own thread IDs.
|
||||
if err := tx.
|
||||
NewSelect().
|
||||
Table("statuses").
|
||||
Column("thread_id").
|
||||
Where("? = ?", bun.Ident("in_reply_to_uri"), status.InReplyToURI).
|
||||
Scan(ctx, &ids); err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
return gtserror.Newf("error selecting status siblings: %w", err)
|
||||
}
|
||||
|
||||
// Append possible IDs to threads slice.
|
||||
threadIDs = append(threadIDs, ids...)
|
||||
}
|
||||
|
||||
if !*status.Local {
|
||||
var ids []string
|
||||
|
||||
// For remote statuses specifically, check to
|
||||
// see if any children are stored for this new
|
||||
// stored parent with their own thread IDs.
|
||||
if err := tx.
|
||||
NewSelect().
|
||||
Table("statuses").
|
||||
Column("thread_id").
|
||||
Where("? = ?", bun.Ident("in_reply_to_uri"), status.URI).
|
||||
Scan(ctx, &ids); err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
return gtserror.Newf("error selecting status children: %w", err)
|
||||
}
|
||||
|
||||
// Append possible IDs to threads slice.
|
||||
threadIDs = append(threadIDs, ids...)
|
||||
}
|
||||
|
||||
// Ensure only *unique* posssible thread IDs.
|
||||
threadIDs = xslices.Deduplicate(threadIDs)
|
||||
switch len(threadIDs) {
|
||||
|
||||
case 0:
|
||||
// No related status with thread ID already exists,
|
||||
// so create new thread ID from status creation time.
|
||||
threadID := id.NewULIDFromTime(status.CreatedAt)
|
||||
|
||||
// Insert new thread.
|
||||
if _, err := tx.
|
||||
NewInsert().
|
||||
Model(>smodel.StatusToEmoji{
|
||||
StatusID: status.ID,
|
||||
EmojiID: i,
|
||||
}).
|
||||
On("CONFLICT (?, ?) DO NOTHING", bun.Ident("status_id"), bun.Ident("emoji_id")).
|
||||
Model(>smodel.Thread{ID: threadID}).
|
||||
Exec(ctx); err != nil {
|
||||
if !errors.Is(err, db.ErrAlreadyExists) {
|
||||
return err
|
||||
}
|
||||
return gtserror.Newf("error inserting thread: %w", err)
|
||||
}
|
||||
|
||||
// Update status thread ID.
|
||||
status.ThreadID = threadID
|
||||
|
||||
case 1:
|
||||
// Inherit single known thread.
|
||||
status.ThreadID = threadIDs[0]
|
||||
|
||||
default:
|
||||
var err error
|
||||
log.Infof(ctx, "reconciling status threading for %s: [%s]", status.URI, strings.Join(threadIDs, ","))
|
||||
status.ThreadID, err = s.fixStatusThreading(ctx, tx, threadIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// create links between this status and any tags it uses
|
||||
for _, i := range status.TagIDs {
|
||||
if _, err := tx.
|
||||
NewInsert().
|
||||
Model(>smodel.StatusToTag{
|
||||
StatusID: status.ID,
|
||||
TagID: i,
|
||||
}).
|
||||
On("CONFLICT (?, ?) DO NOTHING", bun.Ident("status_id"), bun.Ident("tag_id")).
|
||||
Exec(ctx); err != nil {
|
||||
if !errors.Is(err, db.ErrAlreadyExists) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// change the status ID of the media
|
||||
// attachments to the current status
|
||||
for _, a := range status.Attachments {
|
||||
a.StatusID = status.ID
|
||||
if _, err := tx.
|
||||
NewUpdate().
|
||||
Model(a).
|
||||
Column("status_id").
|
||||
Where("? = ?", bun.Ident("media_attachment.id"), a.ID).
|
||||
Exec(ctx); err != nil {
|
||||
if !errors.Is(err, db.ErrAlreadyExists) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the status is threaded, create
|
||||
// link between thread and status.
|
||||
if status.ThreadID != "" {
|
||||
if _, err := tx.
|
||||
NewInsert().
|
||||
Model(>smodel.ThreadToStatus{
|
||||
ThreadID: status.ThreadID,
|
||||
StatusID: status.ID,
|
||||
}).
|
||||
On("CONFLICT (?, ?) DO NOTHING", bun.Ident("thread_id"), bun.Ident("status_id")).
|
||||
Exec(ctx); err != nil {
|
||||
if !errors.Is(err, db.ErrAlreadyExists) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, insert the status
|
||||
_, err := tx.NewInsert().
|
||||
Model(status).
|
||||
Exec(ctx)
|
||||
return err
|
||||
// And after threading, insert status.
|
||||
// This will error if ThreadID is unset.
|
||||
return insertStatus(ctx, tx, status)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// fixStatusThreading can be called to reconcile statuses in the same thread but known to be using multiple given threads.
|
||||
func (s *statusDB) fixStatusThreading(ctx context.Context, tx bun.Tx, threadIDs []string) (string, error) {
|
||||
if len(threadIDs) <= 1 {
|
||||
panic("invalid call to fixStatusThreading()")
|
||||
}
|
||||
|
||||
// Sort ascending, i.e.
|
||||
// oldest thread ID first.
|
||||
slices.Sort(threadIDs)
|
||||
|
||||
// Drop the oldest thread ID
|
||||
// from slice, we'll keep this.
|
||||
threadID := threadIDs[0]
|
||||
threadIDs = threadIDs[1:]
|
||||
|
||||
// On updates, gather IDs of changed model
|
||||
// IDs for later stage of cache invalidation,
|
||||
// preallocating slices for worst-case scenarios.
|
||||
statusIDs := make([]string, 0, 4*len(threadIDs))
|
||||
muteIDs := make([]string, 0, 4*len(threadIDs))
|
||||
|
||||
// Update all statuses with
|
||||
// thread IDs to use oldest.
|
||||
if _, err := tx.
|
||||
NewUpdate().
|
||||
Table("statuses").
|
||||
Where("? IN (?)", bun.Ident("thread_id"), bun.In(threadIDs)).
|
||||
Set("? = ?", bun.Ident("thread_id"), threadID).
|
||||
Returning("?", bun.Ident("id")).
|
||||
Exec(ctx, &statusIDs); err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
return "", gtserror.Newf("error updating statuses: %w", err)
|
||||
}
|
||||
|
||||
// Update all thread mutes with
|
||||
// thread IDs to use oldest.
|
||||
if _, err := tx.
|
||||
NewUpdate().
|
||||
Table("thread_mutes").
|
||||
Where("? IN (?)", bun.Ident("thread_id"), bun.In(threadIDs)).
|
||||
Set("? = ?", bun.Ident("thread_id"), threadID).
|
||||
Returning("?", bun.Ident("id")).
|
||||
Exec(ctx, &muteIDs); err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
return "", gtserror.Newf("error updating thread mutes: %w", err)
|
||||
}
|
||||
|
||||
// Delete all now
|
||||
// unused thread IDs.
|
||||
if _, err := tx.
|
||||
NewDelete().
|
||||
Table("threads").
|
||||
Where("? IN (?)", bun.Ident("id"), bun.In(threadIDs)).
|
||||
Exec(ctx); err != nil {
|
||||
return "", gtserror.Newf("error deleting threads: %w", err)
|
||||
}
|
||||
|
||||
// Invalidate caches for changed statuses and mutes.
|
||||
s.state.Caches.DB.Status.InvalidateIDs("ID", statusIDs)
|
||||
s.state.Caches.DB.ThreadMute.InvalidateIDs("ID", muteIDs)
|
||||
|
||||
return threadID, nil
|
||||
}
|
||||
|
||||
// insertStatus handles the base status insert logic, that is the status itself,
|
||||
// any intermediary table links, and updating media attachments to point to status.
|
||||
func insertStatus(ctx context.Context, tx bun.Tx, status *gtsmodel.Status) error {
|
||||
|
||||
// create links between this
|
||||
// status and any emojis it uses
|
||||
for _, id := range status.EmojiIDs {
|
||||
if _, err := tx.
|
||||
NewInsert().
|
||||
Model(>smodel.StatusToEmoji{
|
||||
StatusID: status.ID,
|
||||
EmojiID: id,
|
||||
}).
|
||||
Exec(ctx); err != nil {
|
||||
return gtserror.Newf("error inserting status_to_emoji: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// create links between this
|
||||
// status and any tags it uses
|
||||
for _, id := range status.TagIDs {
|
||||
if _, err := tx.
|
||||
NewInsert().
|
||||
Model(>smodel.StatusToTag{
|
||||
StatusID: status.ID,
|
||||
TagID: id,
|
||||
}).
|
||||
Exec(ctx); err != nil {
|
||||
return gtserror.Newf("error inserting status_to_tag: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// change the status ID of the media
|
||||
// attachments to the current status
|
||||
for _, a := range status.Attachments {
|
||||
a.StatusID = status.ID
|
||||
if _, err := tx.
|
||||
NewUpdate().
|
||||
Model(a).
|
||||
Column("status_id").
|
||||
Where("? = ?", bun.Ident("media_attachment.id"), a.ID).
|
||||
Exec(ctx); err != nil {
|
||||
return gtserror.Newf("error updating media: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, insert the status
|
||||
if _, err := tx.NewInsert().
|
||||
Model(status).
|
||||
Exec(ctx); err != nil {
|
||||
return gtserror.Newf("error inserting status: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *statusDB) UpdateStatus(ctx context.Context, status *gtsmodel.Status, columns ...string) error {
|
||||
return s.state.Caches.DB.Status.Store(status, func() error {
|
||||
// It is safe to run this database transaction within cache.Store
|
||||
// as the cache does not attempt a mutex lock until AFTER hook.
|
||||
//
|
||||
return s.db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||
// create links between this status and any emojis it uses
|
||||
for _, i := range status.EmojiIDs {
|
||||
|
||||
// create links between this
|
||||
// status and any emojis it uses
|
||||
for _, id := range status.EmojiIDs {
|
||||
if _, err := tx.
|
||||
NewInsert().
|
||||
Model(>smodel.StatusToEmoji{
|
||||
StatusID: status.ID,
|
||||
EmojiID: i,
|
||||
EmojiID: id,
|
||||
}).
|
||||
On("CONFLICT (?, ?) DO NOTHING", bun.Ident("status_id"), bun.Ident("emoji_id")).
|
||||
Exec(ctx); err != nil {
|
||||
if !errors.Is(err, db.ErrAlreadyExists) {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// create links between this status and any tags it uses
|
||||
for _, i := range status.TagIDs {
|
||||
// create links between this
|
||||
// status and any tags it uses
|
||||
for _, id := range status.TagIDs {
|
||||
if _, err := tx.
|
||||
NewInsert().
|
||||
Model(>smodel.StatusToTag{
|
||||
StatusID: status.ID,
|
||||
TagID: i,
|
||||
TagID: id,
|
||||
}).
|
||||
On("CONFLICT (?, ?) DO NOTHING", bun.Ident("status_id"), bun.Ident("tag_id")).
|
||||
Exec(ctx); err != nil {
|
||||
if !errors.Is(err, db.ErrAlreadyExists) {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -457,26 +628,7 @@ func (s *statusDB) UpdateStatus(ctx context.Context, status *gtsmodel.Status, co
|
|||
Column("status_id").
|
||||
Where("? = ?", bun.Ident("media_attachment.id"), a.ID).
|
||||
Exec(ctx); err != nil {
|
||||
if !errors.Is(err, db.ErrAlreadyExists) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the status is threaded, create
|
||||
// link between thread and status.
|
||||
if status.ThreadID != "" {
|
||||
if _, err := tx.
|
||||
NewInsert().
|
||||
Model(>smodel.ThreadToStatus{
|
||||
ThreadID: status.ThreadID,
|
||||
StatusID: status.ID,
|
||||
}).
|
||||
On("CONFLICT (?, ?) DO NOTHING", bun.Ident("thread_id"), bun.Ident("status_id")).
|
||||
Exec(ctx); err != nil {
|
||||
if !errors.Is(err, db.ErrAlreadyExists) {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -499,7 +651,9 @@ func (s *statusDB) DeleteStatusByID(ctx context.Context, id string) error {
|
|||
|
||||
// Delete status from database and any related links in a transaction.
|
||||
if err := s.db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||
// delete links between this status and any emojis it uses
|
||||
|
||||
// delete links between this
|
||||
// status and any emojis it uses
|
||||
if _, err := tx.
|
||||
NewDelete().
|
||||
TableExpr("? AS ?", bun.Ident("status_to_emojis"), bun.Ident("status_to_emoji")).
|
||||
|
|
@ -508,7 +662,8 @@ func (s *statusDB) DeleteStatusByID(ctx context.Context, id string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// delete links between this status and any tags it uses
|
||||
// delete links between this
|
||||
// status and any tags it uses
|
||||
if _, err := tx.
|
||||
NewDelete().
|
||||
TableExpr("? AS ?", bun.Ident("status_to_tags"), bun.Ident("status_to_tag")).
|
||||
|
|
@ -517,16 +672,6 @@ func (s *statusDB) DeleteStatusByID(ctx context.Context, id string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Delete links between this status
|
||||
// and any threads it was a part of.
|
||||
if _, err := tx.
|
||||
NewDelete().
|
||||
TableExpr("? AS ?", bun.Ident("thread_to_statuses"), bun.Ident("thread_to_status")).
|
||||
Where("? = ?", bun.Ident("thread_to_status.status_id"), id).
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// delete the status itself
|
||||
if _, err := tx.
|
||||
NewDelete().
|
||||
|
|
|
|||
|
|
@ -21,8 +21,12 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"code.superseriousbusiness.org/gotosocial/internal/ap"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/db"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/gtscontext"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/id"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/util"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
|
|
@ -253,6 +257,302 @@ func (suite *StatusTestSuite) TestPutPopulatedStatus() {
|
|||
)
|
||||
}
|
||||
|
||||
func (suite *StatusTestSuite) TestPutStatusThreadingBoostOfIDSet() {
|
||||
ctx := suite.T().Context()
|
||||
|
||||
// Fake account details.
|
||||
accountID := id.NewULID()
|
||||
accountURI := "https://example.com/users/" + accountID
|
||||
|
||||
var err error
|
||||
|
||||
// Prepare new status.
|
||||
statusID := id.NewULID()
|
||||
statusURI := accountURI + "/statuses/" + statusID
|
||||
status := >smodel.Status{
|
||||
ID: statusID,
|
||||
URI: statusURI,
|
||||
AccountID: accountID,
|
||||
AccountURI: accountURI,
|
||||
Local: util.Ptr(false),
|
||||
Federated: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
}
|
||||
|
||||
// Insert original status into database.
|
||||
err = suite.db.PutStatus(ctx, status)
|
||||
suite.NoError(err)
|
||||
suite.NotEmpty(status.ThreadID)
|
||||
|
||||
// Prepare new boost.
|
||||
boostID := id.NewULID()
|
||||
boostURI := accountURI + "/statuses/" + boostID
|
||||
boost := >smodel.Status{
|
||||
ID: boostID,
|
||||
URI: boostURI,
|
||||
AccountID: accountID,
|
||||
AccountURI: accountURI,
|
||||
BoostOfID: statusID,
|
||||
BoostOfAccountID: accountID,
|
||||
Local: util.Ptr(false),
|
||||
Federated: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
}
|
||||
|
||||
// Insert boost wrapper into database.
|
||||
err = suite.db.PutStatus(ctx, boost)
|
||||
suite.NoError(err)
|
||||
|
||||
// Boost wrapper should have inherited thread.
|
||||
suite.Equal(status.ThreadID, boost.ThreadID)
|
||||
}
|
||||
|
||||
func (suite *StatusTestSuite) TestPutStatusThreadingInReplyToIDSet() {
|
||||
ctx := suite.T().Context()
|
||||
|
||||
// Fake account details.
|
||||
accountID := id.NewULID()
|
||||
accountURI := "https://example.com/users/" + accountID
|
||||
|
||||
var err error
|
||||
|
||||
// Prepare new status.
|
||||
statusID := id.NewULID()
|
||||
statusURI := accountURI + "/statuses/" + statusID
|
||||
status := >smodel.Status{
|
||||
ID: statusID,
|
||||
URI: statusURI,
|
||||
AccountID: accountID,
|
||||
AccountURI: accountURI,
|
||||
Local: util.Ptr(false),
|
||||
Federated: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
}
|
||||
|
||||
// Insert original status into database.
|
||||
err = suite.db.PutStatus(ctx, status)
|
||||
suite.NoError(err)
|
||||
suite.NotEmpty(status.ThreadID)
|
||||
|
||||
// Prepare new reply.
|
||||
replyID := id.NewULID()
|
||||
replyURI := accountURI + "/statuses/" + replyID
|
||||
reply := >smodel.Status{
|
||||
ID: replyID,
|
||||
URI: replyURI,
|
||||
AccountID: accountID,
|
||||
AccountURI: accountURI,
|
||||
InReplyToID: statusID,
|
||||
InReplyToURI: statusURI,
|
||||
InReplyToAccountID: accountID,
|
||||
Local: util.Ptr(false),
|
||||
Federated: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
}
|
||||
|
||||
// Insert status reply into database.
|
||||
err = suite.db.PutStatus(ctx, reply)
|
||||
suite.NoError(err)
|
||||
|
||||
// Status reply should have inherited thread.
|
||||
suite.Equal(status.ThreadID, reply.ThreadID)
|
||||
}
|
||||
|
||||
func (suite *StatusTestSuite) TestPutStatusThreadingSiblings() {
|
||||
ctx := suite.T().Context()
|
||||
|
||||
// Fake account details.
|
||||
accountID := id.NewULID()
|
||||
accountURI := "https://example.com/users/" + accountID
|
||||
|
||||
// Main parent status ID.
|
||||
statusID := id.NewULID()
|
||||
statusURI := accountURI + "/statuses/" + statusID
|
||||
status := >smodel.Status{
|
||||
ID: statusID,
|
||||
URI: statusURI,
|
||||
AccountID: accountID,
|
||||
AccountURI: accountURI,
|
||||
Local: util.Ptr(false),
|
||||
Federated: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
}
|
||||
|
||||
const siblingCount = 10
|
||||
var statuses []*gtsmodel.Status
|
||||
for range siblingCount {
|
||||
id := id.NewULID()
|
||||
uri := accountURI + "/statuses/" + id
|
||||
|
||||
// Note here that inReplyToID not being set,
|
||||
// so as they get inserted it's as if children
|
||||
// are being dereferenced ahead of stored parent.
|
||||
//
|
||||
// Which is where out-of-sync threads can occur.
|
||||
statuses = append(statuses, >smodel.Status{
|
||||
ID: id,
|
||||
URI: uri,
|
||||
AccountID: accountID,
|
||||
AccountURI: accountURI,
|
||||
InReplyToURI: statusURI,
|
||||
Local: util.Ptr(false),
|
||||
Federated: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
})
|
||||
}
|
||||
|
||||
var err error
|
||||
var threadID string
|
||||
|
||||
// Insert all of the sibling children
|
||||
// into the database, they should all
|
||||
// still get correctly threaded together.
|
||||
for _, child := range statuses {
|
||||
err = suite.db.PutStatus(ctx, child)
|
||||
suite.NoError(err)
|
||||
suite.NotEmpty(child.ThreadID)
|
||||
if threadID == "" {
|
||||
threadID = child.ThreadID
|
||||
} else {
|
||||
suite.Equal(threadID, child.ThreadID)
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, insert the parent status.
|
||||
err = suite.db.PutStatus(ctx, status)
|
||||
suite.NoError(err)
|
||||
|
||||
// Parent should have inherited thread.
|
||||
suite.Equal(threadID, status.ThreadID)
|
||||
}
|
||||
|
||||
func (suite *StatusTestSuite) TestPutStatusThreadingReconcile() {
|
||||
ctx := suite.T().Context()
|
||||
|
||||
// Fake account details.
|
||||
accountID := id.NewULID()
|
||||
accountURI := "https://example.com/users/" + accountID
|
||||
|
||||
const threadLength = 10
|
||||
var statuses []*gtsmodel.Status
|
||||
var lastURI, lastID string
|
||||
|
||||
// Generate front-half of thread.
|
||||
for range threadLength / 2 {
|
||||
id := id.NewULID()
|
||||
uri := accountURI + "/statuses/" + id
|
||||
statuses = append(statuses, >smodel.Status{
|
||||
ID: id,
|
||||
URI: uri,
|
||||
AccountID: accountID,
|
||||
AccountURI: accountURI,
|
||||
InReplyToID: lastID,
|
||||
InReplyToURI: lastURI,
|
||||
Local: util.Ptr(false),
|
||||
Federated: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
})
|
||||
lastURI = uri
|
||||
lastID = id
|
||||
}
|
||||
|
||||
// Generate back-half of thread.
|
||||
//
|
||||
// Note here that inReplyToID not being set past
|
||||
// the first item, so as they get inserted it's
|
||||
// as if the children are dereferenced ahead of
|
||||
// the stored parent, i.e. an out-of-sync thread.
|
||||
for range threadLength / 2 {
|
||||
id := id.NewULID()
|
||||
uri := accountURI + "/statuses/" + id
|
||||
statuses = append(statuses, >smodel.Status{
|
||||
ID: id,
|
||||
URI: uri,
|
||||
AccountID: accountID,
|
||||
AccountURI: accountURI,
|
||||
InReplyToID: lastID,
|
||||
InReplyToURI: lastURI,
|
||||
Local: util.Ptr(false),
|
||||
Federated: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
})
|
||||
lastURI = uri
|
||||
lastID = ""
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
// Thread IDs we expect to see for
|
||||
// head statuses as we add them, and
|
||||
// for tail statuses as we add them.
|
||||
var thread0, threadN string
|
||||
|
||||
// Insert status thread from head and tail,
|
||||
// specifically stopping before the middle.
|
||||
// These should each get threaded separately.
|
||||
for i := range (threadLength / 2) - 1 {
|
||||
i0, iN := i, len(statuses)-1-i
|
||||
|
||||
// Insert i'th status from the start.
|
||||
err = suite.db.PutStatus(ctx, statuses[i0])
|
||||
suite.NoError(err)
|
||||
suite.NotEmpty(statuses[i0].ThreadID)
|
||||
|
||||
// Check i0 thread.
|
||||
if thread0 == "" {
|
||||
thread0 = statuses[i0].ThreadID
|
||||
} else {
|
||||
suite.Equal(thread0, statuses[i0].ThreadID)
|
||||
}
|
||||
|
||||
// Insert i'th status from the end.
|
||||
err = suite.db.PutStatus(ctx, statuses[iN])
|
||||
suite.NoError(err)
|
||||
suite.NotEmpty(statuses[iN].ThreadID)
|
||||
|
||||
// Check iN thread.
|
||||
if threadN == "" {
|
||||
threadN = statuses[iN].ThreadID
|
||||
} else {
|
||||
suite.Equal(threadN, statuses[iN].ThreadID)
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, insert remaining statuses,
|
||||
// at some point among these it should
|
||||
// trigger a status thread reconcile.
|
||||
for _, status := range statuses {
|
||||
|
||||
if status.ThreadID != "" {
|
||||
// already inserted
|
||||
continue
|
||||
}
|
||||
|
||||
// Insert remaining status into db.
|
||||
err = suite.db.PutStatus(ctx, status)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
// The reconcile should pick the older,
|
||||
// i.e. smaller of two ULID thread IDs.
|
||||
finalThreadID := min(thread0, threadN)
|
||||
for _, status := range statuses {
|
||||
|
||||
// Get ID of status.
|
||||
id := status.ID
|
||||
|
||||
// Fetch latest status the from database.
|
||||
status, err := suite.db.GetStatusByID(
|
||||
gtscontext.SetBarebones(ctx),
|
||||
id,
|
||||
)
|
||||
suite.NoError(err)
|
||||
|
||||
// Ensure after reconcile uses expected thread.
|
||||
suite.Equal(finalThreadID, status.ThreadID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatusTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(StatusTestSuite))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ type Status interface {
|
|||
// PopulateStatusEdits ensures that status' edits are fully popualted.
|
||||
PopulateStatusEdits(ctx context.Context, status *gtsmodel.Status) error
|
||||
|
||||
// PutStatus stores one status in the database.
|
||||
// PutStatus stores one status in the database, this also handles status threading.
|
||||
PutStatus(ctx context.Context, status *gtsmodel.Status) error
|
||||
|
||||
// UpdateStatus updates one status in the database.
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ func (d *Dereferencer) EnrichAnnounce(
|
|||
// Generate an ID for the boost wrapper status.
|
||||
boost.ID = id.NewULIDFromTime(boost.CreatedAt)
|
||||
|
||||
// Store the boost wrapper status in database.
|
||||
// Store the remote boost wrapper status in database.
|
||||
switch err = d.state.DB.PutStatus(ctx, boost); {
|
||||
case err == nil:
|
||||
// all groovy.
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import (
|
|||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"code.superseriousbusiness.org/gotosocial/internal/ap"
|
||||
|
|
@ -571,15 +570,6 @@ func (d *Dereferencer) enrichStatus(
|
|||
return nil, nil, gtserror.Newf("error populating mentions for status %s: %w", uri, err)
|
||||
}
|
||||
|
||||
// Ensure status in a thread is connected.
|
||||
threadChanged, err := d.threadStatus(ctx,
|
||||
status,
|
||||
latestStatus,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, gtserror.Newf("error handling threading for status %s: %w", uri, err)
|
||||
}
|
||||
|
||||
// Populate tags associated with status, passing
|
||||
// in existing status to reuse old where possible.
|
||||
tagsChanged, err := d.fetchStatusTags(ctx,
|
||||
|
|
@ -614,7 +604,7 @@ func (d *Dereferencer) enrichStatus(
|
|||
}
|
||||
|
||||
if isNew {
|
||||
// Simplest case, insert this new status into the database.
|
||||
// Simplest case, insert this new remote status into the database.
|
||||
if err := d.state.DB.PutStatus(ctx, latestStatus); err != nil {
|
||||
return nil, nil, gtserror.Newf("error inserting new status %s: %w", uri, err)
|
||||
}
|
||||
|
|
@ -627,7 +617,6 @@ func (d *Dereferencer) enrichStatus(
|
|||
latestStatus,
|
||||
pollChanged,
|
||||
mentionsChanged,
|
||||
threadChanged,
|
||||
tagsChanged,
|
||||
mediaChanged,
|
||||
emojiChanged,
|
||||
|
|
@ -736,81 +725,6 @@ func (d *Dereferencer) fetchStatusMentions(
|
|||
return changed, nil
|
||||
}
|
||||
|
||||
// threadStatus ensures that given status is threaded correctly
|
||||
// where necessary. that is it will inherit a thread ID from the
|
||||
// existing copy if it is threaded correctly, else it will inherit
|
||||
// a thread ID from a parent with existing thread, else it will
|
||||
// generate a new thread ID if status mentions a local account.
|
||||
func (d *Dereferencer) threadStatus(
|
||||
ctx context.Context,
|
||||
existing *gtsmodel.Status,
|
||||
status *gtsmodel.Status,
|
||||
) (
|
||||
changed bool,
|
||||
err error,
|
||||
) {
|
||||
|
||||
// Check for existing status
|
||||
// that is already threaded.
|
||||
if existing.ThreadID != "" {
|
||||
|
||||
// Existing is threaded correctly.
|
||||
if existing.InReplyTo == nil ||
|
||||
existing.InReplyTo.ThreadID == existing.ThreadID {
|
||||
status.ThreadID = existing.ThreadID
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// TODO: delete incorrect thread
|
||||
}
|
||||
|
||||
// Check for existing parent to inherit threading from.
|
||||
if inReplyTo := status.InReplyTo; inReplyTo != nil &&
|
||||
inReplyTo.ThreadID != "" {
|
||||
status.ThreadID = inReplyTo.ThreadID
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Parent wasn't threaded. If this
|
||||
// status mentions a local account,
|
||||
// we should thread it so that local
|
||||
// account can mute it if they want.
|
||||
mentionsLocal := slices.ContainsFunc(
|
||||
status.Mentions,
|
||||
func(m *gtsmodel.Mention) bool {
|
||||
// If TargetAccount couldn't
|
||||
// be deref'd, we know it's not
|
||||
// a local account, so only
|
||||
// check for non-nil accounts.
|
||||
return m.TargetAccount != nil &&
|
||||
m.TargetAccount.IsLocal()
|
||||
},
|
||||
)
|
||||
|
||||
if !mentionsLocal {
|
||||
// Status doesn't mention a
|
||||
// local account, so we don't
|
||||
// need to thread it.
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Status mentions a local account.
|
||||
// Create a new thread and assign
|
||||
// it to the status.
|
||||
threadID := id.NewULID()
|
||||
|
||||
// Insert new thread model into db.
|
||||
if err := d.state.DB.PutThread(ctx,
|
||||
>smodel.Thread{ID: threadID},
|
||||
); err != nil {
|
||||
return false, gtserror.Newf("error inserting new thread in db: %w", err)
|
||||
}
|
||||
|
||||
// Set thread on latest status.
|
||||
status.ThreadID = threadID
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// fetchStatusTags populates the tags on 'status', fetching existing
|
||||
// from the database and creating new where needed. 'existing' is used
|
||||
// to fetch tags that have not changed since previous stored status.
|
||||
|
|
@ -1135,7 +1049,6 @@ func (d *Dereferencer) handleStatusEdit(
|
|||
status *gtsmodel.Status,
|
||||
pollChanged bool,
|
||||
mentionsChanged bool,
|
||||
threadChanged bool,
|
||||
tagsChanged bool,
|
||||
mediaChanged bool,
|
||||
emojiChanged bool,
|
||||
|
|
@ -1193,14 +1106,6 @@ func (d *Dereferencer) handleStatusEdit(
|
|||
// been previously populated properly.
|
||||
}
|
||||
|
||||
if threadChanged {
|
||||
cols = append(cols, "thread_id")
|
||||
|
||||
// Thread changed doesn't necessarily
|
||||
// indicate an edit, it may just now
|
||||
// actually be included in a thread.
|
||||
}
|
||||
|
||||
if tagsChanged {
|
||||
cols = append(cols, "tags") // i.e. TagIDs
|
||||
|
||||
|
|
|
|||
|
|
@ -27,56 +27,56 @@ import (
|
|||
|
||||
// Status represents a user-created 'post' or 'status' in the database, either remote or local
|
||||
type Status struct {
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
EditedAt time.Time `bun:"type:timestamptz,nullzero"` // when this status was last edited (if set)
|
||||
FetchedAt time.Time `bun:"type:timestamptz,nullzero"` // when was item (remote) last fetched.
|
||||
PinnedAt time.Time `bun:"type:timestamptz,nullzero"` // Status was pinned by owning account at this time.
|
||||
URI string `bun:",unique,nullzero,notnull"` // activitypub URI of this status
|
||||
URL string `bun:",nullzero"` // web url for viewing this status
|
||||
Content string `bun:""` // Content HTML for this status.
|
||||
AttachmentIDs []string `bun:"attachments,array"` // Database IDs of any media attachments associated with this status
|
||||
Attachments []*MediaAttachment `bun:"attached_media,rel:has-many"` // Attachments corresponding to attachmentIDs
|
||||
TagIDs []string `bun:"tags,array"` // Database IDs of any tags used in this status
|
||||
Tags []*Tag `bun:"attached_tags,m2m:status_to_tags"` // Tags corresponding to tagIDs. https://bun.uptrace.dev/guide/relations.html#many-to-many-relation
|
||||
MentionIDs []string `bun:"mentions,array"` // Database IDs of any mentions in this status
|
||||
Mentions []*Mention `bun:"attached_mentions,rel:has-many"` // Mentions corresponding to mentionIDs
|
||||
EmojiIDs []string `bun:"emojis,array"` // Database IDs of any emojis used in this status
|
||||
Emojis []*Emoji `bun:"attached_emojis,m2m:status_to_emojis"` // Emojis corresponding to emojiIDs. https://bun.uptrace.dev/guide/relations.html#many-to-many-relation
|
||||
Local *bool `bun:",nullzero,notnull,default:false"` // is this status from a local account?
|
||||
AccountID string `bun:"type:CHAR(26),nullzero,notnull"` // which account posted this status?
|
||||
Account *Account `bun:"rel:belongs-to"` // account corresponding to accountID
|
||||
AccountURI string `bun:",nullzero,notnull"` // activitypub uri of the owner of this status
|
||||
InReplyToID string `bun:"type:CHAR(26),nullzero"` // id of the status this status replies to
|
||||
InReplyToURI string `bun:",nullzero"` // activitypub uri of the status this status is a reply to
|
||||
InReplyToAccountID string `bun:"type:CHAR(26),nullzero"` // id of the account that this status replies to
|
||||
InReplyTo *Status `bun:"-"` // status corresponding to inReplyToID
|
||||
InReplyToAccount *Account `bun:"rel:belongs-to"` // account corresponding to inReplyToAccountID
|
||||
BoostOfID string `bun:"type:CHAR(26),nullzero"` // id of the status this status is a boost of
|
||||
BoostOfURI string `bun:"-"` // URI of the status this status is a boost of; field not inserted in the db, just for dereferencing purposes.
|
||||
BoostOfAccountID string `bun:"type:CHAR(26),nullzero"` // id of the account that owns the boosted status
|
||||
BoostOf *Status `bun:"-"` // status that corresponds to boostOfID
|
||||
BoostOfAccount *Account `bun:"rel:belongs-to"` // account that corresponds to boostOfAccountID
|
||||
ThreadID string `bun:"type:CHAR(26),nullzero"` // id of the thread to which this status belongs; only set for remote statuses if a local account is involved at some point in the thread, otherwise null
|
||||
EditIDs []string `bun:"edits,array"` //
|
||||
Edits []*StatusEdit `bun:"-"` //
|
||||
PollID string `bun:"type:CHAR(26),nullzero"` //
|
||||
Poll *Poll `bun:"-"` //
|
||||
ContentWarning string `bun:",nullzero"` // Content warning HTML for this status.
|
||||
ContentWarningText string `bun:""` // Original text of the content warning without formatting
|
||||
Visibility Visibility `bun:",nullzero,notnull"` // visibility entry for this status
|
||||
Sensitive *bool `bun:",nullzero,notnull,default:false"` // mark the status as sensitive?
|
||||
Language string `bun:",nullzero"` // what language is this status written in?
|
||||
CreatedWithApplicationID string `bun:"type:CHAR(26),nullzero"` // Which application was used to create this status?
|
||||
CreatedWithApplication *Application `bun:"rel:belongs-to"` // application corresponding to createdWithApplicationID
|
||||
ActivityStreamsType string `bun:",nullzero,notnull"` // What is the activitystreams type of this status? See: https://www.w3.org/TR/activitystreams-vocabulary/#object-types. Will probably almost always be Note but who knows!.
|
||||
Text string `bun:""` // Original text of the status without formatting
|
||||
ContentType StatusContentType `bun:",nullzero"` // Content type used to process the original text of the status
|
||||
Federated *bool `bun:",notnull"` // This status will be federated beyond the local timeline(s)
|
||||
InteractionPolicy *InteractionPolicy `bun:""` // InteractionPolicy for this status. If null then the default InteractionPolicy should be assumed for this status's Visibility. Always null for boost wrappers.
|
||||
PendingApproval *bool `bun:",nullzero,notnull,default:false"` // If true then status is a reply or boost wrapper that must be Approved by the reply-ee or boost-ee before being fully distributed.
|
||||
PreApproved bool `bun:"-"` // If true, then status is a reply to or boost wrapper of a status on our instance, has permission to do the interaction, and an Accept should be sent out for it immediately. Field not stored in the DB.
|
||||
ApprovedByURI string `bun:",nullzero"` // URI of an Accept Activity that approves the Announce or Create Activity that this status was/will be attached to.
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
EditedAt time.Time `bun:"type:timestamptz,nullzero"` // when this status was last edited (if set)
|
||||
FetchedAt time.Time `bun:"type:timestamptz,nullzero"` // when was item (remote) last fetched.
|
||||
PinnedAt time.Time `bun:"type:timestamptz,nullzero"` // Status was pinned by owning account at this time.
|
||||
URI string `bun:",unique,nullzero,notnull"` // activitypub URI of this status
|
||||
URL string `bun:",nullzero"` // web url for viewing this status
|
||||
Content string `bun:""` // Content HTML for this status.
|
||||
AttachmentIDs []string `bun:"attachments,array"` // Database IDs of any media attachments associated with this status
|
||||
Attachments []*MediaAttachment `bun:"attached_media,rel:has-many"` // Attachments corresponding to attachmentIDs
|
||||
TagIDs []string `bun:"tags,array"` // Database IDs of any tags used in this status
|
||||
Tags []*Tag `bun:"attached_tags,m2m:status_to_tags"` // Tags corresponding to tagIDs. https://bun.uptrace.dev/guide/relations.html#many-to-many-relation
|
||||
MentionIDs []string `bun:"mentions,array"` // Database IDs of any mentions in this status
|
||||
Mentions []*Mention `bun:"attached_mentions,rel:has-many"` // Mentions corresponding to mentionIDs
|
||||
EmojiIDs []string `bun:"emojis,array"` // Database IDs of any emojis used in this status
|
||||
Emojis []*Emoji `bun:"attached_emojis,m2m:status_to_emojis"` // Emojis corresponding to emojiIDs. https://bun.uptrace.dev/guide/relations.html#many-to-many-relation
|
||||
Local *bool `bun:",nullzero,notnull,default:false"` // is this status from a local account?
|
||||
AccountID string `bun:"type:CHAR(26),nullzero,notnull"` // which account posted this status?
|
||||
Account *Account `bun:"rel:belongs-to"` // account corresponding to accountID
|
||||
AccountURI string `bun:",nullzero,notnull"` // activitypub uri of the owner of this status
|
||||
InReplyToID string `bun:"type:CHAR(26),nullzero"` // id of the status this status replies to
|
||||
InReplyToURI string `bun:",nullzero"` // activitypub uri of the status this status is a reply to
|
||||
InReplyToAccountID string `bun:"type:CHAR(26),nullzero"` // id of the account that this status replies to
|
||||
InReplyTo *Status `bun:"-"` // status corresponding to inReplyToID
|
||||
InReplyToAccount *Account `bun:"rel:belongs-to"` // account corresponding to inReplyToAccountID
|
||||
BoostOfID string `bun:"type:CHAR(26),nullzero"` // id of the status this status is a boost of
|
||||
BoostOfURI string `bun:"-"` // URI of the status this status is a boost of; field not inserted in the db, just for dereferencing purposes.
|
||||
BoostOfAccountID string `bun:"type:CHAR(26),nullzero"` // id of the account that owns the boosted status
|
||||
BoostOf *Status `bun:"-"` // status that corresponds to boostOfID
|
||||
BoostOfAccount *Account `bun:"rel:belongs-to"` // account that corresponds to boostOfAccountID
|
||||
ThreadID string `bun:"type:CHAR(26),nullzero,notnull,default:00000000000000000000000000"` // id of the thread to which this status belongs
|
||||
EditIDs []string `bun:"edits,array"` //
|
||||
Edits []*StatusEdit `bun:"-"` //
|
||||
PollID string `bun:"type:CHAR(26),nullzero"` //
|
||||
Poll *Poll `bun:"-"` //
|
||||
ContentWarning string `bun:",nullzero"` // Content warning HTML for this status.
|
||||
ContentWarningText string `bun:""` // Original text of the content warning without formatting
|
||||
Visibility Visibility `bun:",nullzero,notnull"` // visibility entry for this status
|
||||
Sensitive *bool `bun:",nullzero,notnull,default:false"` // mark the status as sensitive?
|
||||
Language string `bun:",nullzero"` // what language is this status written in?
|
||||
CreatedWithApplicationID string `bun:"type:CHAR(26),nullzero"` // Which application was used to create this status?
|
||||
CreatedWithApplication *Application `bun:"rel:belongs-to"` // application corresponding to createdWithApplicationID
|
||||
ActivityStreamsType string `bun:",nullzero,notnull"` // What is the activitystreams type of this status? See: https://www.w3.org/TR/activitystreams-vocabulary/#object-types. Will probably almost always be Note but who knows!.
|
||||
Text string `bun:""` // Original text of the status without formatting
|
||||
ContentType StatusContentType `bun:",nullzero"` // Content type used to process the original text of the status
|
||||
Federated *bool `bun:",notnull"` // This status will be federated beyond the local timeline(s)
|
||||
InteractionPolicy *InteractionPolicy `bun:""` // InteractionPolicy for this status. If null then the default InteractionPolicy should be assumed for this status's Visibility. Always null for boost wrappers.
|
||||
PendingApproval *bool `bun:",nullzero,notnull,default:false"` // If true then status is a reply or boost wrapper that must be Approved by the reply-ee or boost-ee before being fully distributed.
|
||||
PreApproved bool `bun:"-"` // If true, then status is a reply to or boost wrapper of a status on our instance, has permission to do the interaction, and an Accept should be sent out for it immediately. Field not stored in the DB.
|
||||
ApprovedByURI string `bun:",nullzero"` // URI of an Accept Activity that approves the Announce or Create Activity that this status was/will be attached to.
|
||||
}
|
||||
|
||||
// GetID implements timeline.Timelineable{}.
|
||||
|
|
|
|||
|
|
@ -23,10 +23,3 @@ type Thread struct {
|
|||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
StatusIDs []string `bun:"-"` // ids of statuses belonging to this thread (order not guaranteed)
|
||||
}
|
||||
|
||||
// ThreadToStatus is an intermediate struct to facilitate the
|
||||
// many2many relationship between a thread and one or more statuses.
|
||||
type ThreadToStatus struct {
|
||||
ThreadID string `bun:"type:CHAR(26),unique:statusthread,nullzero,notnull"`
|
||||
StatusID string `bun:"type:CHAR(26),unique:statusthread,nullzero,notnull"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import (
|
|||
"hash"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
apimodel "code.superseriousbusiness.org/gotosocial/internal/api/model"
|
||||
|
|
@ -35,6 +36,7 @@ import (
|
|||
"code.superseriousbusiness.org/gotosocial/internal/gtserror"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/log"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/oauth"
|
||||
"codeberg.org/gruf/go-bitutil"
|
||||
"codeberg.org/gruf/go-byteutil"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
|
@ -60,49 +62,79 @@ func NoLLaMas(
|
|||
return func(*gin.Context) {}
|
||||
}
|
||||
|
||||
seed := make([]byte, 32)
|
||||
var seed [32]byte
|
||||
|
||||
// Read random data for the token seed.
|
||||
_, err := io.ReadFull(rand.Reader, seed)
|
||||
_, err := io.ReadFull(rand.Reader, seed[:])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Configure nollamas.
|
||||
var nollamas nollamas
|
||||
nollamas.seed = seed
|
||||
nollamas.entropy = seed
|
||||
nollamas.ttl = time.Hour
|
||||
nollamas.diff = config.GetAdvancedScraperDeterrenceDifficulty()
|
||||
nollamas.rounds = config.GetAdvancedScraperDeterrenceDifficulty()
|
||||
nollamas.getInstanceV1 = getInstanceV1
|
||||
nollamas.policy = cookiePolicy
|
||||
return nollamas.Serve
|
||||
}
|
||||
|
||||
// i.e. hash slice length.
|
||||
const hashLen = sha256.Size
|
||||
|
||||
// i.e. hex.EncodedLen(hashLen).
|
||||
const encodedHashLen = 2 * hashLen
|
||||
|
||||
// hashWithBufs encompasses a hash along
|
||||
// with the necessary buffers to generate
|
||||
// a hashsum and then encode that sum.
|
||||
type hashWithBufs struct {
|
||||
hash hash.Hash
|
||||
hbuf []byte
|
||||
ebuf []byte
|
||||
hbuf [hashLen]byte
|
||||
ebuf [encodedHashLen]byte
|
||||
}
|
||||
|
||||
// write is a passthrough to hash.Hash{}.Write().
|
||||
func (h *hashWithBufs) write(b []byte) {
|
||||
_, _ = h.hash.Write(b)
|
||||
}
|
||||
|
||||
// writeString is a passthrough to hash.Hash{}.Write([]byte(s)).
|
||||
func (h *hashWithBufs) writeString(s string) {
|
||||
_, _ = h.hash.Write(byteutil.S2B(s))
|
||||
}
|
||||
|
||||
// EncodedSum returns the hex encoded sum of hash.Sum().
|
||||
func (h *hashWithBufs) EncodedSum() string {
|
||||
_ = h.hash.Sum(h.hbuf[:0])
|
||||
hex.Encode(h.ebuf[:], h.hbuf[:])
|
||||
return string(h.ebuf[:])
|
||||
}
|
||||
|
||||
// Reset will reset hash and buffers.
|
||||
func (h *hashWithBufs) Reset() {
|
||||
h.ebuf = [encodedHashLen]byte{}
|
||||
h.hbuf = [hashLen]byte{}
|
||||
h.hash.Reset()
|
||||
}
|
||||
|
||||
type nollamas struct {
|
||||
// our instance cookie policy.
|
||||
policy apiutil.CookiePolicy
|
||||
|
||||
// unique token seed
|
||||
// unique entropy
|
||||
// to prevent hashes
|
||||
// being guessable
|
||||
seed []byte
|
||||
entropy [32]byte
|
||||
|
||||
// success cookie TTL
|
||||
ttl time.Duration
|
||||
|
||||
// algorithm difficulty knobs.
|
||||
// diff determines the number
|
||||
// of leading zeroes required.
|
||||
diff uint8
|
||||
// rounds determines roughly how
|
||||
// many hash-encode rounds each
|
||||
// client is required to complete.
|
||||
rounds uint32
|
||||
|
||||
// extra fields required for
|
||||
// our template rendering.
|
||||
|
|
@ -134,18 +166,8 @@ func (m *nollamas) Serve(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
// i.e. outputted hash slice length.
|
||||
const hashLen = sha256.Size
|
||||
|
||||
// i.e. hex.EncodedLen(hashLen).
|
||||
const encodedHashLen = 2 * hashLen
|
||||
|
||||
// Prepare hash + buffers.
|
||||
hash := hashWithBufs{
|
||||
hash: sha256.New(),
|
||||
hbuf: make([]byte, 0, hashLen),
|
||||
ebuf: make([]byte, encodedHashLen),
|
||||
}
|
||||
// Prepare new hash with buffers.
|
||||
hash := hashWithBufs{hash: sha256.New()}
|
||||
|
||||
// Extract client fingerprint data.
|
||||
userAgent := c.GetHeader("User-Agent")
|
||||
|
|
@ -153,15 +175,7 @@ func (m *nollamas) Serve(c *gin.Context) {
|
|||
|
||||
// Generate a unique token for this request,
|
||||
// only valid for a period of now +- m.ttl.
|
||||
token := m.token(&hash, userAgent, clientIP)
|
||||
|
||||
// For unique challenge string just use a
|
||||
// single portion of their 'success' token.
|
||||
// SHA256 is not yet cracked, this is not an
|
||||
// application of a hash requiring serious
|
||||
// cryptographic security and it rotates on
|
||||
// a TTL basis, so it should be fine.
|
||||
challenge := token[:len(token)/4]
|
||||
token := m.getToken(&hash, userAgent, clientIP)
|
||||
|
||||
// Check for a provided success token.
|
||||
cookie, _ := c.Cookie("gts-nollamas")
|
||||
|
|
@ -169,8 +183,8 @@ func (m *nollamas) Serve(c *gin.Context) {
|
|||
// Check whether passed cookie
|
||||
// is the expected success token.
|
||||
if subtle.ConstantTimeCompare(
|
||||
byteutil.S2B(token),
|
||||
byteutil.S2B(cookie),
|
||||
byteutil.S2B(token),
|
||||
) == 1 {
|
||||
|
||||
// They passed us a valid, expected
|
||||
|
|
@ -185,10 +199,15 @@ func (m *nollamas) Serve(c *gin.Context) {
|
|||
// handlers from being called.
|
||||
c.Abort()
|
||||
|
||||
// Generate challenge for this unique (yet deterministic) token,
|
||||
// returning seed, wanted 'challenge' result and expected solution.
|
||||
seed, challenge, solution := m.getChallenge(&hash, token)
|
||||
|
||||
// Prepare new log entry.
|
||||
l := log.WithContext(ctx).
|
||||
WithField("userAgent", userAgent).
|
||||
WithField("challenge", challenge)
|
||||
WithField("seed", seed).
|
||||
WithField("rounds", solution)
|
||||
|
||||
// Extract and parse query.
|
||||
query := c.Request.URL.Query()
|
||||
|
|
@ -196,32 +215,28 @@ func (m *nollamas) Serve(c *gin.Context) {
|
|||
// Check query to see if an in-progress
|
||||
// challenge solution has been provided.
|
||||
nonce := query.Get("nollamas_solution")
|
||||
if nonce == "" || len(nonce) > 20 {
|
||||
if nonce == "" {
|
||||
|
||||
// noting that here, 20 is
|
||||
// max integer string len.
|
||||
//
|
||||
// An invalid solution string, just
|
||||
// present them with new challenge.
|
||||
// No solution given, likely new client!
|
||||
// Simply present them with challenge.
|
||||
m.renderChallenge(c, seed, challenge)
|
||||
l.Info("posing new challenge")
|
||||
m.renderChallenge(c, challenge)
|
||||
return
|
||||
}
|
||||
|
||||
// Reset the hash.
|
||||
hash.hash.Reset()
|
||||
// Check nonce matches expected.
|
||||
if subtle.ConstantTimeCompare(
|
||||
byteutil.S2B(solution),
|
||||
byteutil.S2B(nonce),
|
||||
) != 1 {
|
||||
|
||||
// Check challenge+nonce as possible solution.
|
||||
if !m.checkChallenge(&hash, challenge, nonce) {
|
||||
|
||||
// They failed challenge,
|
||||
// re-present challenge page.
|
||||
l.Info("invalid solution provided")
|
||||
m.renderChallenge(c, challenge)
|
||||
// Their nonce failed, re-challenge them.
|
||||
m.renderChallenge(c, challenge, solution)
|
||||
l.Infof("invalid solution provided: %s", nonce)
|
||||
return
|
||||
}
|
||||
|
||||
l.Infof("challenge passed: %s", nonce)
|
||||
l.Info("challenge passed")
|
||||
|
||||
// Drop solution query and encode.
|
||||
query.Del("nollamas_solution")
|
||||
|
|
@ -233,7 +248,7 @@ func (m *nollamas) Serve(c *gin.Context) {
|
|||
c.Redirect(http.StatusTemporaryRedirect, c.Request.URL.RequestURI())
|
||||
}
|
||||
|
||||
func (m *nollamas) renderChallenge(c *gin.Context, challenge string) {
|
||||
func (m *nollamas) renderChallenge(c *gin.Context, seed, challenge string) {
|
||||
// Fetch current instance information for templating vars.
|
||||
instance, errWithCode := m.getInstanceV1(c.Request.Context())
|
||||
if errWithCode != nil {
|
||||
|
|
@ -252,8 +267,8 @@ func (m *nollamas) renderChallenge(c *gin.Context, challenge string) {
|
|||
"/assets/Fork-Awesome/css/fork-awesome.min.css",
|
||||
},
|
||||
Extra: map[string]any{
|
||||
"challenge": challenge,
|
||||
"difficulty": m.diff,
|
||||
"seed": seed,
|
||||
"challenge": challenge,
|
||||
},
|
||||
Javascript: []apiutil.JavascriptEntry{
|
||||
{
|
||||
|
|
@ -264,23 +279,25 @@ func (m *nollamas) renderChallenge(c *gin.Context, challenge string) {
|
|||
})
|
||||
}
|
||||
|
||||
func (m *nollamas) token(hash *hashWithBufs, userAgent, clientIP string) string {
|
||||
// Use our unique seed to seed hash,
|
||||
// getToken generates a unique yet deterministic token for given HTTP request
|
||||
// details, seeded by runtime generated entropy data and ttl rounded timestamp.
|
||||
func (m *nollamas) getToken(hash *hashWithBufs, userAgent, clientIP string) string {
|
||||
|
||||
// Reset before
|
||||
// using hash.
|
||||
hash.Reset()
|
||||
|
||||
// Use our unique entropy to seed hash,
|
||||
// to ensure we have cryptographically
|
||||
// unique, yet deterministic, tokens
|
||||
// generated for a given http client.
|
||||
hash.hash.Write(m.seed)
|
||||
|
||||
// Include difficulty level in
|
||||
// hash input data so if config
|
||||
// changes then token invalidates.
|
||||
hash.hash.Write([]byte{m.diff})
|
||||
hash.write(m.entropy[:])
|
||||
|
||||
// Also seed the generated input with
|
||||
// current time rounded to TTL, so our
|
||||
// single comparison handles expiries.
|
||||
now := time.Now().Round(m.ttl).Unix()
|
||||
hash.hash.Write([]byte{
|
||||
hash.write([]byte{
|
||||
byte(now >> 56),
|
||||
byte(now >> 48),
|
||||
byte(now >> 40),
|
||||
|
|
@ -291,37 +308,78 @@ func (m *nollamas) token(hash *hashWithBufs, userAgent, clientIP string) string
|
|||
byte(now),
|
||||
})
|
||||
|
||||
// Finally, append unique client request data.
|
||||
hash.hash.Write(byteutil.S2B(userAgent))
|
||||
hash.hash.Write(byteutil.S2B(clientIP))
|
||||
// Append client request data.
|
||||
hash.writeString(userAgent)
|
||||
hash.writeString(clientIP)
|
||||
|
||||
// Return hex encoded hash output.
|
||||
hash.hbuf = hash.hash.Sum(hash.hbuf[:0])
|
||||
hex.Encode(hash.ebuf, hash.hbuf)
|
||||
return string(hash.ebuf)
|
||||
// Return hex encoded hash.
|
||||
return hash.EncodedSum()
|
||||
}
|
||||
|
||||
func (m *nollamas) checkChallenge(hash *hashWithBufs, challenge, nonce string) bool {
|
||||
// Hash and encode input challenge with
|
||||
// proposed nonce as a possible solution.
|
||||
hash.hash.Write(byteutil.S2B(challenge))
|
||||
hash.hash.Write(byteutil.S2B(nonce))
|
||||
hash.hbuf = hash.hash.Sum(hash.hbuf[:0])
|
||||
hex.Encode(hash.ebuf, hash.hbuf)
|
||||
solution := hash.ebuf
|
||||
// getChallenge prepares a new challenge given the deterministic input token for this request.
|
||||
// it will return an input seed string, a challenge string which is the end result the client
|
||||
// should be looking for, and the solution for this such that challenge = hex(sha256(seed + solution)).
|
||||
// the solution will always be a string-encoded 64bit integer calculated from m.rounds + random jitter.
|
||||
func (m *nollamas) getChallenge(hash *hashWithBufs, token string) (seed, challenge, solution string) {
|
||||
|
||||
// Compiler bound-check hint.
|
||||
if len(solution) < int(m.diff) {
|
||||
panic(gtserror.New("BCE"))
|
||||
// For their unique seed string just use a
|
||||
// single portion of their 'success' token.
|
||||
// SHA256 is not yet cracked, this is not an
|
||||
// application of a hash requiring serious
|
||||
// cryptographic security and it rotates on
|
||||
// a TTL basis, so it should be fine.
|
||||
seed = token[:len(token)/4]
|
||||
|
||||
// BEFORE resetting the hash, get the last
|
||||
// two bytes of NON-hex-encoded data from
|
||||
// token generation to use for random jitter.
|
||||
// This is taken from the end of the hash as
|
||||
// this is the "unseen" end part of token.
|
||||
//
|
||||
// (if we used hex-encoded data it would
|
||||
// only ever be '0-9' or 'a-z' ASCII chars).
|
||||
//
|
||||
// Security-wise, same applies as-above.
|
||||
jitter := int16(hash.hbuf[len(hash.hbuf)-2]) |
|
||||
int16(hash.hbuf[len(hash.hbuf)-1])<<8
|
||||
|
||||
var rounds int64
|
||||
switch {
|
||||
// For some small percentage of
|
||||
// clients we purposely low-ball
|
||||
// their rounds required, to make
|
||||
// it so gaming it with a starting
|
||||
// nonce value may suddenly fail.
|
||||
case jitter%37 == 0:
|
||||
rounds = int64(m.rounds/10) + int64(jitter/10)
|
||||
case jitter%31 == 0:
|
||||
rounds = int64(m.rounds/5) + int64(jitter/5)
|
||||
case jitter%29 == 0:
|
||||
rounds = int64(m.rounds/3) + int64(jitter/3)
|
||||
case jitter%13 == 0:
|
||||
rounds = int64(m.rounds/2) + int64(jitter/2)
|
||||
|
||||
// Determine an appropriate number of hash rounds
|
||||
// we want the client to perform on input seed. This
|
||||
// is determined as configured m.rounds +- jitter.
|
||||
// This will be the 'solution' to create 'challenge'.
|
||||
default:
|
||||
rounds = int64(m.rounds) + int64(jitter) //nolint:gosec
|
||||
}
|
||||
|
||||
// Check that the first 'diff'
|
||||
// many chars are indeed zeroes.
|
||||
for i := range m.diff {
|
||||
if solution[i] != '0' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
// Encode (positive) determined hash rounds as string.
|
||||
solution = strconv.FormatInt(bitutil.Abs64(rounds), 10)
|
||||
|
||||
return true
|
||||
// Reset before
|
||||
// using hash.
|
||||
hash.Reset()
|
||||
|
||||
// Calculate the expected result
|
||||
// of hex(sha256(seed + solution)),
|
||||
// i.e. the proposed 'challenge'.
|
||||
hash.writeString(seed)
|
||||
hash.writeString(solution)
|
||||
challenge = hash.EncodedSum()
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,41 +95,39 @@ func testNoLLaMasMiddleware(t *testing.T, e *gin.Engine, userAgent string) {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
var seed string
|
||||
var challenge string
|
||||
var difficulty uint64
|
||||
|
||||
// Parse output body and find the challenge / difficulty.
|
||||
for _, line := range strings.Split(string(b), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
switch {
|
||||
case strings.HasPrefix(line, "data-nollamas-seed=\""):
|
||||
line = line[20:]
|
||||
line = line[:len(line)-1]
|
||||
seed = line
|
||||
case strings.HasPrefix(line, "data-nollamas-challenge=\""):
|
||||
line = line[25:]
|
||||
line = line[:len(line)-1]
|
||||
challenge = line
|
||||
case strings.HasPrefix(line, "data-nollamas-difficulty=\""):
|
||||
line = line[26:]
|
||||
line = line[:len(line)-1]
|
||||
var err error
|
||||
difficulty, err = strconv.ParseUint(line, 10, 8)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure valid posed challenge.
|
||||
assert.NotZero(t, difficulty)
|
||||
assert.NotEmpty(t, challenge)
|
||||
assert.NotEmpty(t, seed)
|
||||
|
||||
// Prepare a test request for gin engine.
|
||||
r = httptest.NewRequest("GET", "/", nil)
|
||||
r.Header.Set("User-Agent", userAgent)
|
||||
rw = httptest.NewRecorder()
|
||||
|
||||
// Now compute and set solution query paramater.
|
||||
solution := computeSolution(challenge, difficulty)
|
||||
r.URL.RawQuery = "nollamas_solution=" + solution
|
||||
|
||||
t.Logf("seed=%s", seed)
|
||||
t.Logf("challenge=%s", challenge)
|
||||
t.Logf("difficulty=%d", difficulty)
|
||||
|
||||
// Now compute and set solution query paramater.
|
||||
solution := computeSolution(seed, challenge)
|
||||
r.URL.RawQuery = "nollamas_solution=" + solution
|
||||
t.Logf("solution=%s", solution)
|
||||
|
||||
// Pass req through
|
||||
|
|
@ -152,17 +150,14 @@ func testNoLLaMasMiddleware(t *testing.T, e *gin.Engine, userAgent string) {
|
|||
}
|
||||
|
||||
// computeSolution does the functional equivalent of our nollamas workerTask.js.
|
||||
func computeSolution(challenge string, diff uint64) string {
|
||||
outer:
|
||||
func computeSolution(seed, challenge string) string {
|
||||
for i := 0; ; i++ {
|
||||
solution := strconv.Itoa(i)
|
||||
combined := challenge + solution
|
||||
combined := seed + solution
|
||||
hash := sha256.Sum256(byteutil.S2B(combined))
|
||||
encoded := hex.EncodeToString(hash[:])
|
||||
for i := range diff {
|
||||
if encoded[i] != '0' {
|
||||
continue outer
|
||||
}
|
||||
if encoded != challenge {
|
||||
continue
|
||||
}
|
||||
return solution
|
||||
}
|
||||
|
|
|
|||
|
|
@ -217,10 +217,6 @@ func (p *Processor) Create(
|
|||
return nil, errWithCode
|
||||
}
|
||||
|
||||
if errWithCode := p.processThreadID(ctx, status); errWithCode != nil {
|
||||
return nil, errWithCode
|
||||
}
|
||||
|
||||
// Process the incoming created status visibility.
|
||||
processVisibility(form, requester.Settings.Privacy, status)
|
||||
|
||||
|
|
@ -444,46 +440,6 @@ func (p *Processor) processInReplyTo(
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *Processor) processThreadID(ctx context.Context, status *gtsmodel.Status) gtserror.WithCode {
|
||||
// Status takes the thread ID of
|
||||
// whatever it replies to, if set.
|
||||
//
|
||||
// Might not be set if status is local
|
||||
// and replies to a remote status that
|
||||
// doesn't have a thread ID yet.
|
||||
//
|
||||
// If so, we can just thread from this
|
||||
// status onwards instead, since this
|
||||
// is where the relevant part of the
|
||||
// thread starts, from the perspective
|
||||
// of our instance at least.
|
||||
if status.InReplyTo != nil &&
|
||||
status.InReplyTo.ThreadID != "" {
|
||||
// Just inherit threadID from parent.
|
||||
status.ThreadID = status.InReplyTo.ThreadID
|
||||
return nil
|
||||
}
|
||||
|
||||
// Mark new thread (or threaded
|
||||
// subsection) starting from here.
|
||||
threadID := id.NewULID()
|
||||
if err := p.state.DB.PutThread(
|
||||
ctx,
|
||||
>smodel.Thread{
|
||||
ID: threadID,
|
||||
},
|
||||
); err != nil {
|
||||
err := gtserror.Newf("error inserting new thread in db: %w", err)
|
||||
return gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
// Future replies to this status
|
||||
// (if any) will inherit this thread ID.
|
||||
status.ThreadID = threadID
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func processVisibility(
|
||||
form *apimodel.StatusCreateRequest,
|
||||
accountDefaultVis gtsmodel.Visibility,
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ EXPECT=$(cat << "EOF"
|
|||
"127.0.0.1/32"
|
||||
],
|
||||
"advanced-rate-limit-requests": 6969,
|
||||
"advanced-scraper-deterrence-difficulty": 5,
|
||||
"advanced-scraper-deterrence-difficulty": 500000,
|
||||
"advanced-scraper-deterrence-enabled": true,
|
||||
"advanced-sender-multiplier": -1,
|
||||
"advanced-throttling-multiplier": -1,
|
||||
|
|
@ -309,7 +309,7 @@ GTS_SYSLOG_ADDRESS='127.0.0.1:6969' \
|
|||
GTS_ADVANCED_COOKIES_SAMESITE='strict' \
|
||||
GTS_ADVANCED_RATE_LIMIT_EXCEPTIONS="192.0.2.0/24,127.0.0.1/32" \
|
||||
GTS_ADVANCED_RATE_LIMIT_REQUESTS=6969 \
|
||||
GTS_ADVANCED_SCRAPER_DETERRENCE_DIFFICULTY=5 \
|
||||
GTS_ADVANCED_SCRAPER_DETERRENCE_DIFFICULTY=500000 \
|
||||
GTS_ADVANCED_SCRAPER_DETERRENCE_ENABLED=true \
|
||||
GTS_ADVANCED_SENDER_MULTIPLIER=-1 \
|
||||
GTS_ADVANCED_THROTTLING_MULTIPLIER=-1 \
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ func testDefaults() config.Configuration {
|
|||
|
||||
ScraperDeterrence: config.ScraperDeterrenceConfig{
|
||||
Enabled: envBool("GTS_ADVANCED_SCRAPER_DETERRENCE_ENABLED", false),
|
||||
Difficulty: uint8(envInt("GTS_ADVANCED_SCRAPER_DETERRENCE_DIFFICULTY", 4)), //nolint
|
||||
Difficulty: uint32(envInt("GTS_ADVANCED_SCRAPER_DETERRENCE_DIFFICULTY", 100000)), //nolint
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import (
|
|||
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/log"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/state"
|
||||
"codeberg.org/gruf/go-kv"
|
||||
)
|
||||
|
||||
var testModels = []interface{}{
|
||||
|
|
@ -58,7 +59,6 @@ var testModels = []interface{}{
|
|||
>smodel.Tag{},
|
||||
>smodel.Thread{},
|
||||
>smodel.ThreadMute{},
|
||||
>smodel.ThreadToStatus{},
|
||||
>smodel.User{},
|
||||
>smodel.UserMute{},
|
||||
>smodel.VAPIDKeyPair{},
|
||||
|
|
@ -201,7 +201,10 @@ func StandardDBSetup(db db.DB, accounts map[string]*gtsmodel.Account) {
|
|||
|
||||
for _, v := range NewTestStatuses() {
|
||||
if err := db.Put(ctx, v); err != nil {
|
||||
log.Panic(ctx, err)
|
||||
log.PanicKVs(ctx, kv.Fields{
|
||||
{"error", err},
|
||||
{"status", v},
|
||||
}...)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -301,12 +304,6 @@ func StandardDBSetup(db db.DB, accounts map[string]*gtsmodel.Account) {
|
|||
}
|
||||
}
|
||||
|
||||
for _, v := range NewTestThreadToStatus() {
|
||||
if err := db.Put(ctx, v); err != nil {
|
||||
log.Panic(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range NewTestPolls() {
|
||||
if err := db.Put(ctx, v); err != nil {
|
||||
log.Panic(ctx, err)
|
||||
|
|
|
|||
|
|
@ -2154,6 +2154,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
AccountID: "01F8MH17FWEB39HZJ76B6VXSKF",
|
||||
BoostOfID: "01F8MHAMCHF6Y650WCRSCP4WMY",
|
||||
BoostOfAccountID: "01F8MH1H7YV1Z7D2C8K2730QBF",
|
||||
ThreadID: "01JV7NMMYX2Y38ZP3Y9SYJWT36",
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
Sensitive: util.Ptr(false),
|
||||
CreatedWithApplicationID: "01F8MGXQRHYF5QPMTMXP78QC2F",
|
||||
|
|
@ -2312,6 +2313,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/the_mighty_zork",
|
||||
AccountID: "01F8MH1H7YV1Z7D2C8K2730QBF",
|
||||
ThreadID: "01JV7PB3BPGFR13Q9B3XD4DJ5W",
|
||||
Visibility: gtsmodel.VisibilityFollowersOnly,
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
|
|
@ -2378,6 +2380,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/the_mighty_zork",
|
||||
AccountID: "01F8MH1H7YV1Z7D2C8K2730QBF",
|
||||
ThreadID: "01JV7NT07NPSJQC703A4D0FK49",
|
||||
EditIDs: []string{"01JDPZCZ2Y9KSGZW0R7ZG8T8Y2", "01JDPZDADMD1T9HKF94RECF7PP"},
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
Sensitive: util.Ptr(false),
|
||||
|
|
@ -2581,6 +2584,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/1happyturtle",
|
||||
AccountID: "01F8MH5NBDF2MV7CTC4Q5128HF",
|
||||
ThreadID: "01JV7NVEBG7Q27WM66SPMBN3Q5",
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
|
|
@ -2604,6 +2608,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/the_mighty_zork",
|
||||
AccountID: "01F8MH5NBDF2MV7CTC4Q5128HF",
|
||||
ThreadID: "01JV7NW0CD8Q8EWSF1RPC0AZXT",
|
||||
EditIDs: []string{"01JDPZPBXAX0M02YSEPB21KX4R", "01JDPZPJHKP7E3M0YQXEXPS1YT", "01JDPZPY3F85Y7B78ETRXEMWD9"},
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
Sensitive: util.Ptr(false),
|
||||
|
|
@ -2629,6 +2634,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/media_mogul",
|
||||
AccountID: "01JPCMD83Y4WR901094YES3QC5",
|
||||
ThreadID: "01JV7NXDB7Z6YAFX8ZDKP9C20Y",
|
||||
Visibility: gtsmodel.VisibilityUnlocked,
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
|
|
@ -2653,6 +2659,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/media_mogul",
|
||||
AccountID: "01JPCMD83Y4WR901094YES3QC5",
|
||||
ThreadID: "01JV7NXSGST4TYA3SAPADQ04JR",
|
||||
Visibility: gtsmodel.VisibilityUnlocked,
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
|
|
@ -2670,6 +2677,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
Local: util.Ptr(false),
|
||||
AccountURI: "http://fossbros-anonymous.io/users/foss_satan",
|
||||
AccountID: "01F8MH5ZK5VRH73AKHQM6Y9VNX",
|
||||
ThreadID: "01JV7NY908EG95DQPJKTXKHCBW",
|
||||
Visibility: gtsmodel.VisibilityUnlocked,
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
|
|
@ -2687,6 +2695,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
Local: util.Ptr(false),
|
||||
AccountURI: "http://fossbros-anonymous.io/users/foss_satan",
|
||||
AccountID: "01F8MH5ZK5VRH73AKHQM6Y9VNX",
|
||||
ThreadID: "01JV7NYTCE3384MC1GRVC9V0K0",
|
||||
Visibility: gtsmodel.VisibilityUnlocked,
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
|
|
@ -2705,6 +2714,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
Local: util.Ptr(false),
|
||||
AccountURI: "http://fossbros-anonymous.io/users/foss_satan",
|
||||
AccountID: "01F8MH5ZK5VRH73AKHQM6Y9VNX",
|
||||
ThreadID: "01JV7NZ58GGQSVVZMK6P7EBADM",
|
||||
Visibility: gtsmodel.VisibilityUnlocked,
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
|
|
@ -2725,6 +2735,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
Local: util.Ptr(false),
|
||||
AccountURI: "http://fossbros-anonymous.io/users/foss_satan",
|
||||
AccountID: "01F8MH5ZK5VRH73AKHQM6Y9VNX",
|
||||
ThreadID: "01JV7NZWF1J2BVQ7SWMMRBYC58",
|
||||
EditIDs: []string{"01JDQ07ZZ4FGP13YN8TF63P5A6", "01JDQ08AYQC0G6413VAHA51CV9"},
|
||||
PollID: "01JDQ0EZ5HM9T4WXRQ5WSVD40J",
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
|
|
@ -2745,6 +2756,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
|
|||
AccountURI: "http://example.org/users/Some_User",
|
||||
MentionIDs: []string{"01HE7XQNMKTVC8MNPCE1JGK4J3"},
|
||||
AccountID: "01FHMQX3GAABWSM0S2VZEC2SWC",
|
||||
ThreadID: "01HCWDF2Q4HV5QC161C4TGQ0M3",
|
||||
InReplyToID: "01F8MH75CBF9JFX4ZAD54N0W0R",
|
||||
InReplyToAccountID: "01F8MH17FWEB39HZJ76B6VXSKF",
|
||||
InReplyToURI: "http://localhost:8080/users/admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R",
|
||||
|
|
@ -2985,75 +2997,6 @@ func NewTestThreads() map[string]*gtsmodel.Thread {
|
|||
}
|
||||
}
|
||||
|
||||
func NewTestThreadToStatus() []*gtsmodel.ThreadToStatus {
|
||||
return []*gtsmodel.ThreadToStatus{
|
||||
{
|
||||
ThreadID: "01HCWDF2Q4HV5QC161C4TGQ0M3",
|
||||
StatusID: "01F8MH75CBF9JFX4ZAD54N0W0R",
|
||||
},
|
||||
{
|
||||
ThreadID: "01HCWDQ1C7APSEY34B1HFVHVX7",
|
||||
StatusID: "01F8MHAAY43M6RJ473VQFCVH37",
|
||||
},
|
||||
{
|
||||
ThreadID: "01HCWDKKBWECZJQ93E262N36VN",
|
||||
StatusID: "01FF25D5Q0DH7CHD57CTRS6WK0",
|
||||
},
|
||||
{
|
||||
ThreadID: "01HCWDKKBWECZJQ93E262N36VN",
|
||||
StatusID: "01F8MHAMCHF6Y650WCRSCP4WMY",
|
||||
},
|
||||
{
|
||||
ThreadID: "01HCWDVTW3HQWSX66VJQ91Z1RH",
|
||||
StatusID: "01F8MHAYFKS4KMXF8K5Y1C0KRN",
|
||||
},
|
||||
{
|
||||
ThreadID: "01HCWDY9PDNHDBDBBFTJKJY8XE",
|
||||
StatusID: "01F8MHBBN8120SYH7D5S050MGK",
|
||||
},
|
||||
{
|
||||
ThreadID: "01HCWE0H2GKH794Q7GDPANH91Q",
|
||||
StatusID: "01F8MH82FYRXD2RC6108DAJ5HB",
|
||||
},
|
||||
{
|
||||
ThreadID: "01HCWE1ERQSMMVWDD0BE491E2P",
|
||||
StatusID: "01FCTA44PW9H1TB328S9AQXKDS",
|
||||
},
|
||||
{
|
||||
ThreadID: "01HCWE2Q24FWCZE41AS77SDFRZ",
|
||||
StatusID: "01F8MHBQCBTDKN6X5VHGMMN4MA",
|
||||
},
|
||||
{
|
||||
ThreadID: "01HCWE3P291Z3NJEJVFPW0K9ZQ",
|
||||
StatusID: "01F8MHC0H0A7XHTVH5F596ZKBM",
|
||||
},
|
||||
{
|
||||
ThreadID: "01HCWE4P0EW9HBA5WHW97D5YV0",
|
||||
StatusID: "01F8MHC8VWDRBQR0N1BATDDEM5",
|
||||
},
|
||||
{
|
||||
ThreadID: "01HCWDKKBWECZJQ93E262N36VN",
|
||||
StatusID: "01FCQSQ667XHJ9AV9T27SJJSX5",
|
||||
},
|
||||
{
|
||||
ThreadID: "01HCWDKKBWECZJQ93E262N36VN",
|
||||
StatusID: "01J2M1HPFSS54S60Y0KYV23KJE",
|
||||
},
|
||||
{
|
||||
ThreadID: "01HCWE71MGRRDSHBKXFD5DDSWR",
|
||||
StatusID: "01FN3VJGFH10KR7S2PB0GFJZYG",
|
||||
},
|
||||
{
|
||||
ThreadID: "01HCWE7ZNC2SS4P05WA5QYED23",
|
||||
StatusID: "01G20ZM733MGN8J344T4ZDDFY1",
|
||||
},
|
||||
{
|
||||
ThreadID: "01HCWE4P0EW9HBA5WHW97D5YV0",
|
||||
StatusID: "01J5QVB9VC76NPPRQ207GG4DRZ",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewTestMentions returns a map of gts model mentions keyed by their name.
|
||||
func NewTestMentions() map[string]*gtsmodel.Mention {
|
||||
return map[string]*gtsmodel.Mention{
|
||||
|
|
|
|||
9
vendor/codeberg.org/gruf/go-bitutil/LICENSE
generated
vendored
Normal file
9
vendor/codeberg.org/gruf/go-bitutil/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 gruf
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
3
vendor/codeberg.org/gruf/go-bitutil/README.md
generated
vendored
Normal file
3
vendor/codeberg.org/gruf/go-bitutil/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# go-bitutil
|
||||
|
||||
This library provides helpful methods and types for performing typical bitwise operations on integers, e.g. packing/unpacking, bit flags.
|
||||
29
vendor/codeberg.org/gruf/go-bitutil/abs.go
generated
vendored
Normal file
29
vendor/codeberg.org/gruf/go-bitutil/abs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package bitutil
|
||||
|
||||
// Abs8 returns the absolute value of i (calculated without branching).
|
||||
func Abs8(i int8) int8 {
|
||||
const bits = 8
|
||||
u := uint64(i >> (bits - 1))
|
||||
return (i ^ int8(u)) + int8(u&1)
|
||||
}
|
||||
|
||||
// Abs16 returns the absolute value of i (calculated without branching).
|
||||
func Abs16(i int16) int16 {
|
||||
const bits = 16
|
||||
u := uint64(i >> (bits - 1))
|
||||
return (i ^ int16(u)) + int16(u&1)
|
||||
}
|
||||
|
||||
// Abs32 returns the absolute value of i (calculated without branching).
|
||||
func Abs32(i int32) int32 {
|
||||
const bits = 32
|
||||
u := uint64(i >> (bits - 1))
|
||||
return (i ^ int32(u)) + int32(u&1)
|
||||
}
|
||||
|
||||
// Abs64 returns the absolute value of i (calculated without branching).
|
||||
func Abs64(i int64) int64 {
|
||||
const bits = 64
|
||||
u := uint64(i >> (bits - 1))
|
||||
return (i ^ int64(u)) + int64(u&1)
|
||||
}
|
||||
3744
vendor/codeberg.org/gruf/go-bitutil/flag.go
generated
vendored
Normal file
3744
vendor/codeberg.org/gruf/go-bitutil/flag.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
117
vendor/codeberg.org/gruf/go-bitutil/flag.tpl
generated
vendored
Normal file
117
vendor/codeberg.org/gruf/go-bitutil/flag.tpl
generated
vendored
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
package bitutil
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
{{ range $idx, $size := . }}
|
||||
|
||||
// Flags{{ $size.Size }} is a type-casted unsigned integer with helper
|
||||
// methods for easily managing up to {{ $size.Size }} bit-flags.
|
||||
type Flags{{ $size.Size }} uint{{ $size.Size }}
|
||||
|
||||
// Get will fetch the flag bit value at index 'bit'.
|
||||
func (f Flags{{ $size.Size }}) Get(bit uint8) bool {
|
||||
mask := Flags{{ $size.Size }}(1) << bit
|
||||
return (f & mask != 0)
|
||||
}
|
||||
|
||||
// Set will set the flag bit value at index 'bit'.
|
||||
func (f Flags{{ $size.Size }}) Set(bit uint8) Flags{{ $size.Size }} {
|
||||
mask := Flags{{ $size.Size }}(1) << bit
|
||||
return f | mask
|
||||
}
|
||||
|
||||
// Unset will unset the flag bit value at index 'bit'.
|
||||
func (f Flags{{ $size.Size }}) Unset(bit uint8) Flags{{ $size.Size }} {
|
||||
mask := Flags{{ $size.Size }}(1) << bit
|
||||
return f & ^mask
|
||||
}
|
||||
|
||||
{{ range $idx := $size.Bits }}
|
||||
|
||||
// Get{{ $idx }} will fetch the flag bit value at index {{ $idx }}.
|
||||
func (f Flags{{ $size.Size }}) Get{{ $idx }}() bool {
|
||||
const mask = Flags{{ $size.Size }}(1) << {{ $idx }}
|
||||
return (f & mask != 0)
|
||||
}
|
||||
|
||||
// Set{{ $idx }} will set the flag bit value at index {{ $idx }}.
|
||||
func (f Flags{{ $size.Size }}) Set{{ $idx }}() Flags{{ $size.Size }} {
|
||||
const mask = Flags{{ $size.Size }}(1) << {{ $idx }}
|
||||
return f | mask
|
||||
}
|
||||
|
||||
// Unset{{ $idx }} will unset the flag bit value at index {{ $idx }}.
|
||||
func (f Flags{{ $size.Size }}) Unset{{ $idx }}() Flags{{ $size.Size }} {
|
||||
const mask = Flags{{ $size.Size }}(1) << {{ $idx }}
|
||||
return f & ^mask
|
||||
}
|
||||
|
||||
{{ end }}
|
||||
|
||||
// String returns a human readable representation of Flags{{ $size.Size }}.
|
||||
func (f Flags{{ $size.Size }}) String() string {
|
||||
var (
|
||||
i int
|
||||
val bool
|
||||
buf []byte
|
||||
)
|
||||
|
||||
// Make a prealloc est. based on longest-possible value
|
||||
const prealloc = 1+(len("false ")*{{ $size.Size }})-1+1
|
||||
buf = make([]byte, prealloc)
|
||||
|
||||
buf[i] = '{'
|
||||
i++
|
||||
|
||||
{{ range $idx := .Bits }}
|
||||
val = f.Get{{ $idx }}()
|
||||
i += copy(buf[i:], bool2str(val))
|
||||
buf[i] = ' '
|
||||
i++
|
||||
{{ end }}
|
||||
|
||||
buf[i-1] = '}'
|
||||
buf = buf[:i]
|
||||
|
||||
return *(*string)(unsafe.Pointer(&buf))
|
||||
}
|
||||
|
||||
// GoString returns a more verbose human readable representation of Flags{{ $size.Size }}.
|
||||
func (f Flags{{ $size.Size }})GoString() string {
|
||||
var (
|
||||
i int
|
||||
val bool
|
||||
buf []byte
|
||||
)
|
||||
|
||||
// Make a prealloc est. based on longest-possible value
|
||||
const prealloc = len("bitutil.Flags{{ $size.Size }}{")+(len("{{ sub $size.Size 1 }}=false ")*{{ $size.Size }})-1+1
|
||||
buf = make([]byte, prealloc)
|
||||
|
||||
i += copy(buf[i:], "bitutil.Flags{{ $size.Size }}{")
|
||||
|
||||
{{ range $idx := .Bits }}
|
||||
val = f.Get{{ $idx }}()
|
||||
i += copy(buf[i:], "{{ $idx }}=")
|
||||
i += copy(buf[i:], bool2str(val))
|
||||
buf[i] = ' '
|
||||
i++
|
||||
{{ end }}
|
||||
|
||||
buf[i-1] = '}'
|
||||
buf = buf[:i]
|
||||
|
||||
return *(*string)(unsafe.Pointer(&buf))
|
||||
}
|
||||
|
||||
{{ end }}
|
||||
|
||||
func bool2str(b bool) string {
|
||||
if b {
|
||||
return "true"
|
||||
}
|
||||
return "false"
|
||||
}
|
||||
98
vendor/codeberg.org/gruf/go-bitutil/flag_test.tpl
generated
vendored
Normal file
98
vendor/codeberg.org/gruf/go-bitutil/flag_test.tpl
generated
vendored
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
package bitutil_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"codeberg.org/gruf/go-bytes"
|
||||
)
|
||||
|
||||
{{ range $idx, $size := . }}
|
||||
|
||||
func TestFlags{{ $size.Size }}Get(t *testing.T) {
|
||||
var mask, flags bitutil.Flags{{ $size.Size }}
|
||||
|
||||
{{ range $idx := $size.Bits }}
|
||||
|
||||
mask = bitutil.Flags{{ $size.Size }}(1) << {{ $idx }}
|
||||
|
||||
flags = 0
|
||||
|
||||
flags |= mask
|
||||
if !flags.Get({{ $idx }}) {
|
||||
t.Error("failed .Get() set Flags{{ $size.Size }} bit at index {{ $idx }}")
|
||||
}
|
||||
|
||||
flags = ^bitutil.Flags{{ $size.Size }}(0)
|
||||
|
||||
flags &= ^mask
|
||||
if flags.Get({{ $idx }}) {
|
||||
t.Error("failed .Get() unset Flags{{ $size.Size }} bit at index {{ $idx }}")
|
||||
}
|
||||
|
||||
flags = 0
|
||||
|
||||
flags |= mask
|
||||
if !flags.Get{{ $idx }}() {
|
||||
t.Error("failed .Get{{ $idx }}() set Flags{{ $size.Size }} bit at index {{ $idx }}")
|
||||
}
|
||||
|
||||
flags = ^bitutil.Flags{{ $size.Size }}(0)
|
||||
|
||||
flags &= ^mask
|
||||
if flags.Get{{ $idx }}() {
|
||||
t.Error("failed .Get{{ $idx }}() unset Flags{{ $size.Size }} bit at index {{ $idx }}")
|
||||
}
|
||||
|
||||
{{ end }}
|
||||
}
|
||||
|
||||
func TestFlags{{ $size.Size }}Set(t *testing.T) {
|
||||
var mask, flags bitutil.Flags{{ $size.Size }}
|
||||
|
||||
{{ range $idx := $size.Bits }}
|
||||
|
||||
mask = bitutil.Flags{{ $size.Size }}(1) << {{ $idx }}
|
||||
|
||||
flags = 0
|
||||
|
||||
flags = flags.Set({{ $idx }})
|
||||
if flags & mask == 0 {
|
||||
t.Error("failed .Set() Flags{{ $size.Size }} bit at index {{ $idx }}")
|
||||
}
|
||||
|
||||
flags = 0
|
||||
|
||||
flags = flags.Set{{ $idx }}()
|
||||
if flags & mask == 0 {
|
||||
t.Error("failed .Set{{ $idx }}() Flags{{ $size.Size }} bit at index {{ $idx }}")
|
||||
}
|
||||
|
||||
{{ end }}
|
||||
}
|
||||
|
||||
func TestFlags{{ $size.Size }}Unset(t *testing.T) {
|
||||
var mask, flags bitutil.Flags{{ $size.Size }}
|
||||
|
||||
{{ range $idx := $size.Bits }}
|
||||
|
||||
mask = bitutil.Flags{{ $size.Size }}(1) << {{ $idx }}
|
||||
|
||||
flags = ^bitutil.Flags{{ $size.Size }}(0)
|
||||
|
||||
flags = flags.Unset({{ $idx }})
|
||||
if flags & mask != 0 {
|
||||
t.Error("failed .Unset() Flags{{ $size.Size }} bit at index {{ $idx }}")
|
||||
}
|
||||
|
||||
flags = ^bitutil.Flags{{ $size.Size }}(0)
|
||||
|
||||
flags = flags.Unset{{ $idx }}()
|
||||
if flags & mask != 0 {
|
||||
t.Error("failed .Unset{{ $idx }}() Flags{{ $size.Size }} bit at index {{ $idx }}")
|
||||
}
|
||||
|
||||
{{ end }}
|
||||
}
|
||||
|
||||
{{ end }}
|
||||
85
vendor/codeberg.org/gruf/go-bitutil/pack.go
generated
vendored
Normal file
85
vendor/codeberg.org/gruf/go-bitutil/pack.go
generated
vendored
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
package bitutil
|
||||
|
||||
// PackInt8s will pack two signed 8bit integers into an unsigned 16bit integer.
|
||||
func PackInt8s(i1, i2 int8) uint16 {
|
||||
const bits = 8
|
||||
const mask = (1 << bits) - 1
|
||||
return uint16(i1)<<bits | uint16(i2)&mask
|
||||
}
|
||||
|
||||
// UnpackInt8s will unpack two signed 8bit integers from an unsigned 16bit integer.
|
||||
func UnpackInt8s(i uint16) (int8, int8) {
|
||||
const bits = 8
|
||||
const mask = (1 << bits) - 1
|
||||
return int8(i >> bits), int8(i & mask)
|
||||
}
|
||||
|
||||
// PackInt16s will pack two signed 16bit integers into an unsigned 32bit integer.
|
||||
func PackInt16s(i1, i2 int16) uint32 {
|
||||
const bits = 16
|
||||
const mask = (1 << bits) - 1
|
||||
return uint32(i1)<<bits | uint32(i2)&mask
|
||||
}
|
||||
|
||||
// UnpackInt16s will unpack two signed 16bit integers from an unsigned 32bit integer.
|
||||
func UnpackInt16s(i uint32) (int16, int16) {
|
||||
const bits = 16
|
||||
const mask = (1 << bits) - 1
|
||||
return int16(i >> bits), int16(i & mask)
|
||||
}
|
||||
|
||||
// PackInt32s will pack two signed 32bit integers into an unsigned 64bit integer.
|
||||
func PackInt32s(i1, i2 int32) uint64 {
|
||||
const bits = 32
|
||||
const mask = (1 << bits) - 1
|
||||
return uint64(i1)<<bits | uint64(i2)&mask
|
||||
}
|
||||
|
||||
// UnpackInt32s will unpack two signed 32bit integers from an unsigned 64bit integer.
|
||||
func UnpackInt32s(i uint64) (int32, int32) {
|
||||
const bits = 32
|
||||
const mask = (1 << bits) - 1
|
||||
return int32(i >> bits), int32(i & mask)
|
||||
}
|
||||
|
||||
// PackUint8s will pack two unsigned 8bit integers into an unsigned 16bit integer.
|
||||
func PackUint8s(u1, u2 uint8) uint16 {
|
||||
const bits = 8
|
||||
const mask = (1 << bits) - 1
|
||||
return uint16(u1)<<bits | uint16(u2)&mask
|
||||
}
|
||||
|
||||
// UnpackUint8s will unpack two unsigned 8bit integers from an unsigned 16bit integer.
|
||||
func UnpackUint8s(u uint16) (uint8, uint8) {
|
||||
const bits = 8
|
||||
const mask = (1 << bits) - 1
|
||||
return uint8(u >> bits), uint8(u & mask)
|
||||
}
|
||||
|
||||
// PackUint16s will pack two unsigned 16bit integers into an unsigned 32bit integer.
|
||||
func PackUint16s(u1, u2 uint16) uint32 {
|
||||
const bits = 16
|
||||
const mask = (1 << bits) - 1
|
||||
return uint32(u1)<<bits | uint32(u2)&mask
|
||||
}
|
||||
|
||||
// UnpackUint16s will unpack two unsigned 16bit integers from an unsigned 32bit integer.
|
||||
func UnpackUint16s(u uint32) (uint16, uint16) {
|
||||
const bits = 16
|
||||
const mask = (1 << bits) - 1
|
||||
return uint16(u >> bits), uint16(u & mask)
|
||||
}
|
||||
|
||||
// PackUint32s will pack two unsigned 32bit integers into an unsigned 64bit integer.
|
||||
func PackUint32s(u1, u2 uint32) uint64 {
|
||||
const bits = 32
|
||||
const mask = (1 << bits) - 1
|
||||
return uint64(u1)<<bits | uint64(u2)&mask
|
||||
}
|
||||
|
||||
// UnpackUint32s will unpack two unsigned 32bit integers from an unsigned 64bit integer.
|
||||
func UnpackUint32s(u uint64) (uint32, uint32) {
|
||||
const bits = 32
|
||||
const mask = (1 << bits) - 1
|
||||
return uint32(u >> bits), uint32(u & mask)
|
||||
}
|
||||
60
vendor/codeberg.org/gruf/go-bitutil/test.tpl
generated
vendored
Normal file
60
vendor/codeberg.org/gruf/go-bitutil/test.tpl
generated
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
package atomics_test
|
||||
|
||||
import (
|
||||
"atomic"
|
||||
"unsafe"
|
||||
"testing"
|
||||
|
||||
"codeberg.org/gruf/go-atomics"
|
||||
)
|
||||
|
||||
func Test{{ .Name }}StoreLoad(t *testing.T) {
|
||||
for _, test := range {{ .Name }}Tests {
|
||||
val := atomics.New{{ .Name }}()
|
||||
|
||||
val.Store(test.V1)
|
||||
|
||||
if !({{ call .Compare "val.Load()" "test.V1" }}) {
|
||||
t.Fatalf("failed testing .Store and .Load: expect=%v actual=%v", val.Load(), test.V1)
|
||||
}
|
||||
|
||||
val.Store(test.V2)
|
||||
|
||||
if !({{ call .Compare "val.Load()" "test.V2" }}) {
|
||||
t.Fatalf("failed testing .Store and .Load: expect=%v actual=%v", val.Load(), test.V2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test{{ .Name }}CAS(t *testing.T) {
|
||||
for _, test := range {{ .Name }}Tests {
|
||||
val := atomics.New{{ .Name }}()
|
||||
|
||||
val.Store(test.V1)
|
||||
|
||||
if val.CAS(test.V2, test.V1) {
|
||||
t.Fatalf("failed testing negative .CAS: test=%+v state=%v", test, val.Load())
|
||||
}
|
||||
|
||||
if !val.CAS(test.V1, test.V2) {
|
||||
t.Fatalf("failed testing positive .CAS: test=%+v state=%v", test, val.Load())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test{{ .Name }}Swap(t *testing.T) {
|
||||
for _, test := range {{ .Name }}Tests {
|
||||
val := atomics.New{{ .Name }}()
|
||||
|
||||
val.Store(test.V1)
|
||||
|
||||
if !({{ call .Compare "val.Swap(test.V2)" "test.V1" }}) {
|
||||
t.Fatal("failed testing .Swap")
|
||||
}
|
||||
|
||||
if !({{ call .Compare "val.Swap(test.V1)" "test.V2" }}) {
|
||||
t.Fatal("failed testing .Swap")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
62
vendor/github.com/cenkalti/backoff/v4/context.go
generated
vendored
62
vendor/github.com/cenkalti/backoff/v4/context.go
generated
vendored
|
|
@ -1,62 +0,0 @@
|
|||
package backoff
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
// BackOffContext is a backoff policy that stops retrying after the context
|
||||
// is canceled.
|
||||
type BackOffContext interface { // nolint: golint
|
||||
BackOff
|
||||
Context() context.Context
|
||||
}
|
||||
|
||||
type backOffContext struct {
|
||||
BackOff
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// WithContext returns a BackOffContext with context ctx
|
||||
//
|
||||
// ctx must not be nil
|
||||
func WithContext(b BackOff, ctx context.Context) BackOffContext { // nolint: golint
|
||||
if ctx == nil {
|
||||
panic("nil context")
|
||||
}
|
||||
|
||||
if b, ok := b.(*backOffContext); ok {
|
||||
return &backOffContext{
|
||||
BackOff: b.BackOff,
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
return &backOffContext{
|
||||
BackOff: b,
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
func getContext(b BackOff) context.Context {
|
||||
if cb, ok := b.(BackOffContext); ok {
|
||||
return cb.Context()
|
||||
}
|
||||
if tb, ok := b.(*backOffTries); ok {
|
||||
return getContext(tb.delegate)
|
||||
}
|
||||
return context.Background()
|
||||
}
|
||||
|
||||
func (b *backOffContext) Context() context.Context {
|
||||
return b.ctx
|
||||
}
|
||||
|
||||
func (b *backOffContext) NextBackOff() time.Duration {
|
||||
select {
|
||||
case <-b.ctx.Done():
|
||||
return Stop
|
||||
default:
|
||||
return b.BackOff.NextBackOff()
|
||||
}
|
||||
}
|
||||
216
vendor/github.com/cenkalti/backoff/v4/exponential.go
generated
vendored
216
vendor/github.com/cenkalti/backoff/v4/exponential.go
generated
vendored
|
|
@ -1,216 +0,0 @@
|
|||
package backoff
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
ExponentialBackOff is a backoff implementation that increases the backoff
|
||||
period for each retry attempt using a randomization function that grows exponentially.
|
||||
|
||||
NextBackOff() is calculated using the following formula:
|
||||
|
||||
randomized interval =
|
||||
RetryInterval * (random value in range [1 - RandomizationFactor, 1 + RandomizationFactor])
|
||||
|
||||
In other words NextBackOff() will range between the randomization factor
|
||||
percentage below and above the retry interval.
|
||||
|
||||
For example, given the following parameters:
|
||||
|
||||
RetryInterval = 2
|
||||
RandomizationFactor = 0.5
|
||||
Multiplier = 2
|
||||
|
||||
the actual backoff period used in the next retry attempt will range between 1 and 3 seconds,
|
||||
multiplied by the exponential, that is, between 2 and 6 seconds.
|
||||
|
||||
Note: MaxInterval caps the RetryInterval and not the randomized interval.
|
||||
|
||||
If the time elapsed since an ExponentialBackOff instance is created goes past the
|
||||
MaxElapsedTime, then the method NextBackOff() starts returning backoff.Stop.
|
||||
|
||||
The elapsed time can be reset by calling Reset().
|
||||
|
||||
Example: Given the following default arguments, for 10 tries the sequence will be,
|
||||
and assuming we go over the MaxElapsedTime on the 10th try:
|
||||
|
||||
Request # RetryInterval (seconds) Randomized Interval (seconds)
|
||||
|
||||
1 0.5 [0.25, 0.75]
|
||||
2 0.75 [0.375, 1.125]
|
||||
3 1.125 [0.562, 1.687]
|
||||
4 1.687 [0.8435, 2.53]
|
||||
5 2.53 [1.265, 3.795]
|
||||
6 3.795 [1.897, 5.692]
|
||||
7 5.692 [2.846, 8.538]
|
||||
8 8.538 [4.269, 12.807]
|
||||
9 12.807 [6.403, 19.210]
|
||||
10 19.210 backoff.Stop
|
||||
|
||||
Note: Implementation is not thread-safe.
|
||||
*/
|
||||
type ExponentialBackOff struct {
|
||||
InitialInterval time.Duration
|
||||
RandomizationFactor float64
|
||||
Multiplier float64
|
||||
MaxInterval time.Duration
|
||||
// After MaxElapsedTime the ExponentialBackOff returns Stop.
|
||||
// It never stops if MaxElapsedTime == 0.
|
||||
MaxElapsedTime time.Duration
|
||||
Stop time.Duration
|
||||
Clock Clock
|
||||
|
||||
currentInterval time.Duration
|
||||
startTime time.Time
|
||||
}
|
||||
|
||||
// Clock is an interface that returns current time for BackOff.
|
||||
type Clock interface {
|
||||
Now() time.Time
|
||||
}
|
||||
|
||||
// ExponentialBackOffOpts is a function type used to configure ExponentialBackOff options.
|
||||
type ExponentialBackOffOpts func(*ExponentialBackOff)
|
||||
|
||||
// Default values for ExponentialBackOff.
|
||||
const (
|
||||
DefaultInitialInterval = 500 * time.Millisecond
|
||||
DefaultRandomizationFactor = 0.5
|
||||
DefaultMultiplier = 1.5
|
||||
DefaultMaxInterval = 60 * time.Second
|
||||
DefaultMaxElapsedTime = 15 * time.Minute
|
||||
)
|
||||
|
||||
// NewExponentialBackOff creates an instance of ExponentialBackOff using default values.
|
||||
func NewExponentialBackOff(opts ...ExponentialBackOffOpts) *ExponentialBackOff {
|
||||
b := &ExponentialBackOff{
|
||||
InitialInterval: DefaultInitialInterval,
|
||||
RandomizationFactor: DefaultRandomizationFactor,
|
||||
Multiplier: DefaultMultiplier,
|
||||
MaxInterval: DefaultMaxInterval,
|
||||
MaxElapsedTime: DefaultMaxElapsedTime,
|
||||
Stop: Stop,
|
||||
Clock: SystemClock,
|
||||
}
|
||||
for _, fn := range opts {
|
||||
fn(b)
|
||||
}
|
||||
b.Reset()
|
||||
return b
|
||||
}
|
||||
|
||||
// WithInitialInterval sets the initial interval between retries.
|
||||
func WithInitialInterval(duration time.Duration) ExponentialBackOffOpts {
|
||||
return func(ebo *ExponentialBackOff) {
|
||||
ebo.InitialInterval = duration
|
||||
}
|
||||
}
|
||||
|
||||
// WithRandomizationFactor sets the randomization factor to add jitter to intervals.
|
||||
func WithRandomizationFactor(randomizationFactor float64) ExponentialBackOffOpts {
|
||||
return func(ebo *ExponentialBackOff) {
|
||||
ebo.RandomizationFactor = randomizationFactor
|
||||
}
|
||||
}
|
||||
|
||||
// WithMultiplier sets the multiplier for increasing the interval after each retry.
|
||||
func WithMultiplier(multiplier float64) ExponentialBackOffOpts {
|
||||
return func(ebo *ExponentialBackOff) {
|
||||
ebo.Multiplier = multiplier
|
||||
}
|
||||
}
|
||||
|
||||
// WithMaxInterval sets the maximum interval between retries.
|
||||
func WithMaxInterval(duration time.Duration) ExponentialBackOffOpts {
|
||||
return func(ebo *ExponentialBackOff) {
|
||||
ebo.MaxInterval = duration
|
||||
}
|
||||
}
|
||||
|
||||
// WithMaxElapsedTime sets the maximum total time for retries.
|
||||
func WithMaxElapsedTime(duration time.Duration) ExponentialBackOffOpts {
|
||||
return func(ebo *ExponentialBackOff) {
|
||||
ebo.MaxElapsedTime = duration
|
||||
}
|
||||
}
|
||||
|
||||
// WithRetryStopDuration sets the duration after which retries should stop.
|
||||
func WithRetryStopDuration(duration time.Duration) ExponentialBackOffOpts {
|
||||
return func(ebo *ExponentialBackOff) {
|
||||
ebo.Stop = duration
|
||||
}
|
||||
}
|
||||
|
||||
// WithClockProvider sets the clock used to measure time.
|
||||
func WithClockProvider(clock Clock) ExponentialBackOffOpts {
|
||||
return func(ebo *ExponentialBackOff) {
|
||||
ebo.Clock = clock
|
||||
}
|
||||
}
|
||||
|
||||
type systemClock struct{}
|
||||
|
||||
func (t systemClock) Now() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
// SystemClock implements Clock interface that uses time.Now().
|
||||
var SystemClock = systemClock{}
|
||||
|
||||
// Reset the interval back to the initial retry interval and restarts the timer.
|
||||
// Reset must be called before using b.
|
||||
func (b *ExponentialBackOff) Reset() {
|
||||
b.currentInterval = b.InitialInterval
|
||||
b.startTime = b.Clock.Now()
|
||||
}
|
||||
|
||||
// NextBackOff calculates the next backoff interval using the formula:
|
||||
// Randomized interval = RetryInterval * (1 ± RandomizationFactor)
|
||||
func (b *ExponentialBackOff) NextBackOff() time.Duration {
|
||||
// Make sure we have not gone over the maximum elapsed time.
|
||||
elapsed := b.GetElapsedTime()
|
||||
next := getRandomValueFromInterval(b.RandomizationFactor, rand.Float64(), b.currentInterval)
|
||||
b.incrementCurrentInterval()
|
||||
if b.MaxElapsedTime != 0 && elapsed+next > b.MaxElapsedTime {
|
||||
return b.Stop
|
||||
}
|
||||
return next
|
||||
}
|
||||
|
||||
// GetElapsedTime returns the elapsed time since an ExponentialBackOff instance
|
||||
// is created and is reset when Reset() is called.
|
||||
//
|
||||
// The elapsed time is computed using time.Now().UnixNano(). It is
|
||||
// safe to call even while the backoff policy is used by a running
|
||||
// ticker.
|
||||
func (b *ExponentialBackOff) GetElapsedTime() time.Duration {
|
||||
return b.Clock.Now().Sub(b.startTime)
|
||||
}
|
||||
|
||||
// Increments the current interval by multiplying it with the multiplier.
|
||||
func (b *ExponentialBackOff) incrementCurrentInterval() {
|
||||
// Check for overflow, if overflow is detected set the current interval to the max interval.
|
||||
if float64(b.currentInterval) >= float64(b.MaxInterval)/b.Multiplier {
|
||||
b.currentInterval = b.MaxInterval
|
||||
} else {
|
||||
b.currentInterval = time.Duration(float64(b.currentInterval) * b.Multiplier)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a random value from the following interval:
|
||||
// [currentInterval - randomizationFactor * currentInterval, currentInterval + randomizationFactor * currentInterval].
|
||||
func getRandomValueFromInterval(randomizationFactor, random float64, currentInterval time.Duration) time.Duration {
|
||||
if randomizationFactor == 0 {
|
||||
return currentInterval // make sure no randomness is used when randomizationFactor is 0.
|
||||
}
|
||||
var delta = randomizationFactor * float64(currentInterval)
|
||||
var minInterval = float64(currentInterval) - delta
|
||||
var maxInterval = float64(currentInterval) + delta
|
||||
|
||||
// Get a random value from the range [minInterval, maxInterval].
|
||||
// The formula used below has a +1 because if the minInterval is 1 and the maxInterval is 3 then
|
||||
// we want a 33% chance for selecting either 1, 2 or 3.
|
||||
return time.Duration(minInterval + (random * (maxInterval - minInterval + 1)))
|
||||
}
|
||||
146
vendor/github.com/cenkalti/backoff/v4/retry.go
generated
vendored
146
vendor/github.com/cenkalti/backoff/v4/retry.go
generated
vendored
|
|
@ -1,146 +0,0 @@
|
|||
package backoff
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// An OperationWithData is executing by RetryWithData() or RetryNotifyWithData().
|
||||
// The operation will be retried using a backoff policy if it returns an error.
|
||||
type OperationWithData[T any] func() (T, error)
|
||||
|
||||
// An Operation is executing by Retry() or RetryNotify().
|
||||
// The operation will be retried using a backoff policy if it returns an error.
|
||||
type Operation func() error
|
||||
|
||||
func (o Operation) withEmptyData() OperationWithData[struct{}] {
|
||||
return func() (struct{}, error) {
|
||||
return struct{}{}, o()
|
||||
}
|
||||
}
|
||||
|
||||
// Notify is a notify-on-error function. It receives an operation error and
|
||||
// backoff delay if the operation failed (with an error).
|
||||
//
|
||||
// NOTE that if the backoff policy stated to stop retrying,
|
||||
// the notify function isn't called.
|
||||
type Notify func(error, time.Duration)
|
||||
|
||||
// Retry the operation o until it does not return error or BackOff stops.
|
||||
// o is guaranteed to be run at least once.
|
||||
//
|
||||
// If o returns a *PermanentError, the operation is not retried, and the
|
||||
// wrapped error is returned.
|
||||
//
|
||||
// Retry sleeps the goroutine for the duration returned by BackOff after a
|
||||
// failed operation returns.
|
||||
func Retry(o Operation, b BackOff) error {
|
||||
return RetryNotify(o, b, nil)
|
||||
}
|
||||
|
||||
// RetryWithData is like Retry but returns data in the response too.
|
||||
func RetryWithData[T any](o OperationWithData[T], b BackOff) (T, error) {
|
||||
return RetryNotifyWithData(o, b, nil)
|
||||
}
|
||||
|
||||
// RetryNotify calls notify function with the error and wait duration
|
||||
// for each failed attempt before sleep.
|
||||
func RetryNotify(operation Operation, b BackOff, notify Notify) error {
|
||||
return RetryNotifyWithTimer(operation, b, notify, nil)
|
||||
}
|
||||
|
||||
// RetryNotifyWithData is like RetryNotify but returns data in the response too.
|
||||
func RetryNotifyWithData[T any](operation OperationWithData[T], b BackOff, notify Notify) (T, error) {
|
||||
return doRetryNotify(operation, b, notify, nil)
|
||||
}
|
||||
|
||||
// RetryNotifyWithTimer calls notify function with the error and wait duration using the given Timer
|
||||
// for each failed attempt before sleep.
|
||||
// A default timer that uses system timer is used when nil is passed.
|
||||
func RetryNotifyWithTimer(operation Operation, b BackOff, notify Notify, t Timer) error {
|
||||
_, err := doRetryNotify(operation.withEmptyData(), b, notify, t)
|
||||
return err
|
||||
}
|
||||
|
||||
// RetryNotifyWithTimerAndData is like RetryNotifyWithTimer but returns data in the response too.
|
||||
func RetryNotifyWithTimerAndData[T any](operation OperationWithData[T], b BackOff, notify Notify, t Timer) (T, error) {
|
||||
return doRetryNotify(operation, b, notify, t)
|
||||
}
|
||||
|
||||
func doRetryNotify[T any](operation OperationWithData[T], b BackOff, notify Notify, t Timer) (T, error) {
|
||||
var (
|
||||
err error
|
||||
next time.Duration
|
||||
res T
|
||||
)
|
||||
if t == nil {
|
||||
t = &defaultTimer{}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
t.Stop()
|
||||
}()
|
||||
|
||||
ctx := getContext(b)
|
||||
|
||||
b.Reset()
|
||||
for {
|
||||
res, err = operation()
|
||||
if err == nil {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
var permanent *PermanentError
|
||||
if errors.As(err, &permanent) {
|
||||
return res, permanent.Err
|
||||
}
|
||||
|
||||
if next = b.NextBackOff(); next == Stop {
|
||||
if cerr := ctx.Err(); cerr != nil {
|
||||
return res, cerr
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
if notify != nil {
|
||||
notify(err, next)
|
||||
}
|
||||
|
||||
t.Start(next)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return res, ctx.Err()
|
||||
case <-t.C():
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PermanentError signals that the operation should not be retried.
|
||||
type PermanentError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *PermanentError) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
func (e *PermanentError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
func (e *PermanentError) Is(target error) bool {
|
||||
_, ok := target.(*PermanentError)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Permanent wraps the given err in a *PermanentError.
|
||||
func Permanent(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &PermanentError{
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
38
vendor/github.com/cenkalti/backoff/v4/tries.go
generated
vendored
38
vendor/github.com/cenkalti/backoff/v4/tries.go
generated
vendored
|
|
@ -1,38 +0,0 @@
|
|||
package backoff
|
||||
|
||||
import "time"
|
||||
|
||||
/*
|
||||
WithMaxRetries creates a wrapper around another BackOff, which will
|
||||
return Stop if NextBackOff() has been called too many times since
|
||||
the last time Reset() was called
|
||||
|
||||
Note: Implementation is not thread-safe.
|
||||
*/
|
||||
func WithMaxRetries(b BackOff, max uint64) BackOff {
|
||||
return &backOffTries{delegate: b, maxTries: max}
|
||||
}
|
||||
|
||||
type backOffTries struct {
|
||||
delegate BackOff
|
||||
maxTries uint64
|
||||
numTries uint64
|
||||
}
|
||||
|
||||
func (b *backOffTries) NextBackOff() time.Duration {
|
||||
if b.maxTries == 0 {
|
||||
return Stop
|
||||
}
|
||||
if b.maxTries > 0 {
|
||||
if b.maxTries <= b.numTries {
|
||||
return Stop
|
||||
}
|
||||
b.numTries++
|
||||
}
|
||||
return b.delegate.NextBackOff()
|
||||
}
|
||||
|
||||
func (b *backOffTries) Reset() {
|
||||
b.numTries = 0
|
||||
b.delegate.Reset()
|
||||
}
|
||||
29
vendor/github.com/cenkalti/backoff/v5/CHANGELOG.md
generated
vendored
Normal file
29
vendor/github.com/cenkalti/backoff/v5/CHANGELOG.md
generated
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [5.0.0] - 2024-12-19
|
||||
|
||||
### Added
|
||||
|
||||
- RetryAfterError can be returned from an operation to indicate how long to wait before the next retry.
|
||||
|
||||
### Changed
|
||||
|
||||
- Retry function now accepts additional options for specifying max number of tries and max elapsed time.
|
||||
- Retry function now accepts a context.Context.
|
||||
- Operation function signature changed to return result (any type) and error.
|
||||
|
||||
### Removed
|
||||
|
||||
- RetryNotify* and RetryWithData functions. Only single Retry function remains.
|
||||
- Optional arguments from ExponentialBackoff constructor.
|
||||
- Clock and Timer interfaces.
|
||||
|
||||
### Fixed
|
||||
|
||||
- The original error is returned from Retry if there's a PermanentError. (#144)
|
||||
- The Retry function respects the wrapped PermanentError. (#140)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# Exponential Backoff [![GoDoc][godoc image]][godoc] [![Coverage Status][coveralls image]][coveralls]
|
||||
# Exponential Backoff [![GoDoc][godoc image]][godoc]
|
||||
|
||||
This is a Go port of the exponential backoff algorithm from [Google's HTTP Client Library for Java][google-http-java-client].
|
||||
|
||||
|
|
@ -9,9 +9,11 @@ The retries exponentially increase and stop increasing when a certain threshold
|
|||
|
||||
## Usage
|
||||
|
||||
Import path is `github.com/cenkalti/backoff/v4`. Please note the version part at the end.
|
||||
Import path is `github.com/cenkalti/backoff/v5`. Please note the version part at the end.
|
||||
|
||||
Use https://pkg.go.dev/github.com/cenkalti/backoff/v4 to view the documentation.
|
||||
For most cases, use `Retry` function. See [example_test.go][example] for an example.
|
||||
|
||||
If you have specific needs, copy `Retry` function (from [retry.go][retry-src]) into your code and modify it as needed.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
|
@ -19,12 +21,11 @@ Use https://pkg.go.dev/github.com/cenkalti/backoff/v4 to view the documentation.
|
|||
* Please don't send a PR without opening an issue and discussing it first.
|
||||
* If proposed change is not a common use case, I will probably not accept it.
|
||||
|
||||
[godoc]: https://pkg.go.dev/github.com/cenkalti/backoff/v4
|
||||
[godoc]: https://pkg.go.dev/github.com/cenkalti/backoff/v5
|
||||
[godoc image]: https://godoc.org/github.com/cenkalti/backoff?status.png
|
||||
[coveralls]: https://coveralls.io/github/cenkalti/backoff?branch=master
|
||||
[coveralls image]: https://coveralls.io/repos/github/cenkalti/backoff/badge.svg?branch=master
|
||||
|
||||
[google-http-java-client]: https://github.com/google/google-http-java-client/blob/da1aa993e90285ec18579f1553339b00e19b3ab5/google-http-client/src/main/java/com/google/api/client/util/ExponentialBackOff.java
|
||||
[exponential backoff wiki]: http://en.wikipedia.org/wiki/Exponential_backoff
|
||||
|
||||
[advanced example]: https://pkg.go.dev/github.com/cenkalti/backoff/v4?tab=doc#pkg-examples
|
||||
[retry-src]: https://github.com/cenkalti/backoff/blob/v5/retry.go
|
||||
[example]: https://github.com/cenkalti/backoff/blob/v5/example_test.go
|
||||
|
|
@ -15,16 +15,16 @@ import "time"
|
|||
// BackOff is a backoff policy for retrying an operation.
|
||||
type BackOff interface {
|
||||
// NextBackOff returns the duration to wait before retrying the operation,
|
||||
// or backoff. Stop to indicate that no more retries should be made.
|
||||
// backoff.Stop to indicate that no more retries should be made.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// duration := backoff.NextBackOff();
|
||||
// if (duration == backoff.Stop) {
|
||||
// // Do not retry operation.
|
||||
// } else {
|
||||
// // Sleep for duration and retry operation.
|
||||
// }
|
||||
// duration := backoff.NextBackOff()
|
||||
// if duration == backoff.Stop {
|
||||
// // Do not retry operation.
|
||||
// } else {
|
||||
// // Sleep for duration and retry operation.
|
||||
// }
|
||||
//
|
||||
NextBackOff() time.Duration
|
||||
|
||||
46
vendor/github.com/cenkalti/backoff/v5/error.go
generated
vendored
Normal file
46
vendor/github.com/cenkalti/backoff/v5/error.go
generated
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
package backoff
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PermanentError signals that the operation should not be retried.
|
||||
type PermanentError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
// Permanent wraps the given err in a *PermanentError.
|
||||
func Permanent(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &PermanentError{
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
// Error returns a string representation of the Permanent error.
|
||||
func (e *PermanentError) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
// Unwrap returns the wrapped error.
|
||||
func (e *PermanentError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// RetryAfterError signals that the operation should be retried after the given duration.
|
||||
type RetryAfterError struct {
|
||||
Duration time.Duration
|
||||
}
|
||||
|
||||
// RetryAfter returns a RetryAfter error that specifies how long to wait before retrying.
|
||||
func RetryAfter(seconds int) error {
|
||||
return &RetryAfterError{Duration: time.Duration(seconds) * time.Second}
|
||||
}
|
||||
|
||||
// Error returns a string representation of the RetryAfter error.
|
||||
func (e *RetryAfterError) Error() string {
|
||||
return fmt.Sprintf("retry after %s", e.Duration)
|
||||
}
|
||||
125
vendor/github.com/cenkalti/backoff/v5/exponential.go
generated
vendored
Normal file
125
vendor/github.com/cenkalti/backoff/v5/exponential.go
generated
vendored
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
package backoff
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
ExponentialBackOff is a backoff implementation that increases the backoff
|
||||
period for each retry attempt using a randomization function that grows exponentially.
|
||||
|
||||
NextBackOff() is calculated using the following formula:
|
||||
|
||||
randomized interval =
|
||||
RetryInterval * (random value in range [1 - RandomizationFactor, 1 + RandomizationFactor])
|
||||
|
||||
In other words NextBackOff() will range between the randomization factor
|
||||
percentage below and above the retry interval.
|
||||
|
||||
For example, given the following parameters:
|
||||
|
||||
RetryInterval = 2
|
||||
RandomizationFactor = 0.5
|
||||
Multiplier = 2
|
||||
|
||||
the actual backoff period used in the next retry attempt will range between 1 and 3 seconds,
|
||||
multiplied by the exponential, that is, between 2 and 6 seconds.
|
||||
|
||||
Note: MaxInterval caps the RetryInterval and not the randomized interval.
|
||||
|
||||
If the time elapsed since an ExponentialBackOff instance is created goes past the
|
||||
MaxElapsedTime, then the method NextBackOff() starts returning backoff.Stop.
|
||||
|
||||
The elapsed time can be reset by calling Reset().
|
||||
|
||||
Example: Given the following default arguments, for 10 tries the sequence will be,
|
||||
and assuming we go over the MaxElapsedTime on the 10th try:
|
||||
|
||||
Request # RetryInterval (seconds) Randomized Interval (seconds)
|
||||
|
||||
1 0.5 [0.25, 0.75]
|
||||
2 0.75 [0.375, 1.125]
|
||||
3 1.125 [0.562, 1.687]
|
||||
4 1.687 [0.8435, 2.53]
|
||||
5 2.53 [1.265, 3.795]
|
||||
6 3.795 [1.897, 5.692]
|
||||
7 5.692 [2.846, 8.538]
|
||||
8 8.538 [4.269, 12.807]
|
||||
9 12.807 [6.403, 19.210]
|
||||
10 19.210 backoff.Stop
|
||||
|
||||
Note: Implementation is not thread-safe.
|
||||
*/
|
||||
type ExponentialBackOff struct {
|
||||
InitialInterval time.Duration
|
||||
RandomizationFactor float64
|
||||
Multiplier float64
|
||||
MaxInterval time.Duration
|
||||
|
||||
currentInterval time.Duration
|
||||
}
|
||||
|
||||
// Default values for ExponentialBackOff.
|
||||
const (
|
||||
DefaultInitialInterval = 500 * time.Millisecond
|
||||
DefaultRandomizationFactor = 0.5
|
||||
DefaultMultiplier = 1.5
|
||||
DefaultMaxInterval = 60 * time.Second
|
||||
)
|
||||
|
||||
// NewExponentialBackOff creates an instance of ExponentialBackOff using default values.
|
||||
func NewExponentialBackOff() *ExponentialBackOff {
|
||||
return &ExponentialBackOff{
|
||||
InitialInterval: DefaultInitialInterval,
|
||||
RandomizationFactor: DefaultRandomizationFactor,
|
||||
Multiplier: DefaultMultiplier,
|
||||
MaxInterval: DefaultMaxInterval,
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the interval back to the initial retry interval and restarts the timer.
|
||||
// Reset must be called before using b.
|
||||
func (b *ExponentialBackOff) Reset() {
|
||||
b.currentInterval = b.InitialInterval
|
||||
}
|
||||
|
||||
// NextBackOff calculates the next backoff interval using the formula:
|
||||
//
|
||||
// Randomized interval = RetryInterval * (1 ± RandomizationFactor)
|
||||
func (b *ExponentialBackOff) NextBackOff() time.Duration {
|
||||
if b.currentInterval == 0 {
|
||||
b.currentInterval = b.InitialInterval
|
||||
}
|
||||
|
||||
next := getRandomValueFromInterval(b.RandomizationFactor, rand.Float64(), b.currentInterval)
|
||||
b.incrementCurrentInterval()
|
||||
return next
|
||||
}
|
||||
|
||||
// Increments the current interval by multiplying it with the multiplier.
|
||||
func (b *ExponentialBackOff) incrementCurrentInterval() {
|
||||
// Check for overflow, if overflow is detected set the current interval to the max interval.
|
||||
if float64(b.currentInterval) >= float64(b.MaxInterval)/b.Multiplier {
|
||||
b.currentInterval = b.MaxInterval
|
||||
} else {
|
||||
b.currentInterval = time.Duration(float64(b.currentInterval) * b.Multiplier)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a random value from the following interval:
|
||||
//
|
||||
// [currentInterval - randomizationFactor * currentInterval, currentInterval + randomizationFactor * currentInterval].
|
||||
func getRandomValueFromInterval(randomizationFactor, random float64, currentInterval time.Duration) time.Duration {
|
||||
if randomizationFactor == 0 {
|
||||
return currentInterval // make sure no randomness is used when randomizationFactor is 0.
|
||||
}
|
||||
var delta = randomizationFactor * float64(currentInterval)
|
||||
var minInterval = float64(currentInterval) - delta
|
||||
var maxInterval = float64(currentInterval) + delta
|
||||
|
||||
// Get a random value from the range [minInterval, maxInterval].
|
||||
// The formula used below has a +1 because if the minInterval is 1 and the maxInterval is 3 then
|
||||
// we want a 33% chance for selecting either 1, 2 or 3.
|
||||
return time.Duration(minInterval + (random * (maxInterval - minInterval + 1)))
|
||||
}
|
||||
139
vendor/github.com/cenkalti/backoff/v5/retry.go
generated
vendored
Normal file
139
vendor/github.com/cenkalti/backoff/v5/retry.go
generated
vendored
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
package backoff
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DefaultMaxElapsedTime sets a default limit for the total retry duration.
|
||||
const DefaultMaxElapsedTime = 15 * time.Minute
|
||||
|
||||
// Operation is a function that attempts an operation and may be retried.
|
||||
type Operation[T any] func() (T, error)
|
||||
|
||||
// Notify is a function called on operation error with the error and backoff duration.
|
||||
type Notify func(error, time.Duration)
|
||||
|
||||
// retryOptions holds configuration settings for the retry mechanism.
|
||||
type retryOptions struct {
|
||||
BackOff BackOff // Strategy for calculating backoff periods.
|
||||
Timer timer // Timer to manage retry delays.
|
||||
Notify Notify // Optional function to notify on each retry error.
|
||||
MaxTries uint // Maximum number of retry attempts.
|
||||
MaxElapsedTime time.Duration // Maximum total time for all retries.
|
||||
}
|
||||
|
||||
type RetryOption func(*retryOptions)
|
||||
|
||||
// WithBackOff configures a custom backoff strategy.
|
||||
func WithBackOff(b BackOff) RetryOption {
|
||||
return func(args *retryOptions) {
|
||||
args.BackOff = b
|
||||
}
|
||||
}
|
||||
|
||||
// withTimer sets a custom timer for managing delays between retries.
|
||||
func withTimer(t timer) RetryOption {
|
||||
return func(args *retryOptions) {
|
||||
args.Timer = t
|
||||
}
|
||||
}
|
||||
|
||||
// WithNotify sets a notification function to handle retry errors.
|
||||
func WithNotify(n Notify) RetryOption {
|
||||
return func(args *retryOptions) {
|
||||
args.Notify = n
|
||||
}
|
||||
}
|
||||
|
||||
// WithMaxTries limits the number of retry attempts.
|
||||
func WithMaxTries(n uint) RetryOption {
|
||||
return func(args *retryOptions) {
|
||||
args.MaxTries = n
|
||||
}
|
||||
}
|
||||
|
||||
// WithMaxElapsedTime limits the total duration for retry attempts.
|
||||
func WithMaxElapsedTime(d time.Duration) RetryOption {
|
||||
return func(args *retryOptions) {
|
||||
args.MaxElapsedTime = d
|
||||
}
|
||||
}
|
||||
|
||||
// Retry attempts the operation until success, a permanent error, or backoff completion.
|
||||
// It ensures the operation is executed at least once.
|
||||
//
|
||||
// Returns the operation result or error if retries are exhausted or context is cancelled.
|
||||
func Retry[T any](ctx context.Context, operation Operation[T], opts ...RetryOption) (T, error) {
|
||||
// Initialize default retry options.
|
||||
args := &retryOptions{
|
||||
BackOff: NewExponentialBackOff(),
|
||||
Timer: &defaultTimer{},
|
||||
MaxElapsedTime: DefaultMaxElapsedTime,
|
||||
}
|
||||
|
||||
// Apply user-provided options to the default settings.
|
||||
for _, opt := range opts {
|
||||
opt(args)
|
||||
}
|
||||
|
||||
defer args.Timer.Stop()
|
||||
|
||||
startedAt := time.Now()
|
||||
args.BackOff.Reset()
|
||||
for numTries := uint(1); ; numTries++ {
|
||||
// Execute the operation.
|
||||
res, err := operation()
|
||||
if err == nil {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Stop retrying if maximum tries exceeded.
|
||||
if args.MaxTries > 0 && numTries >= args.MaxTries {
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Handle permanent errors without retrying.
|
||||
var permanent *PermanentError
|
||||
if errors.As(err, &permanent) {
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Stop retrying if context is cancelled.
|
||||
if cerr := context.Cause(ctx); cerr != nil {
|
||||
return res, cerr
|
||||
}
|
||||
|
||||
// Calculate next backoff duration.
|
||||
next := args.BackOff.NextBackOff()
|
||||
if next == Stop {
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Reset backoff if RetryAfterError is encountered.
|
||||
var retryAfter *RetryAfterError
|
||||
if errors.As(err, &retryAfter) {
|
||||
next = retryAfter.Duration
|
||||
args.BackOff.Reset()
|
||||
}
|
||||
|
||||
// Stop retrying if maximum elapsed time exceeded.
|
||||
if args.MaxElapsedTime > 0 && time.Since(startedAt)+next > args.MaxElapsedTime {
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Notify on error if a notifier function is provided.
|
||||
if args.Notify != nil {
|
||||
args.Notify(err, next)
|
||||
}
|
||||
|
||||
// Wait for the next backoff period or context cancellation.
|
||||
args.Timer.Start(next)
|
||||
select {
|
||||
case <-args.Timer.C():
|
||||
case <-ctx.Done():
|
||||
return res, context.Cause(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package backoff
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
|
@ -14,8 +13,7 @@ type Ticker struct {
|
|||
C <-chan time.Time
|
||||
c chan time.Time
|
||||
b BackOff
|
||||
ctx context.Context
|
||||
timer Timer
|
||||
timer timer
|
||||
stop chan struct{}
|
||||
stopOnce sync.Once
|
||||
}
|
||||
|
|
@ -27,22 +25,12 @@ type Ticker struct {
|
|||
// provided backoff policy (notably calling NextBackOff or Reset)
|
||||
// while the ticker is running.
|
||||
func NewTicker(b BackOff) *Ticker {
|
||||
return NewTickerWithTimer(b, &defaultTimer{})
|
||||
}
|
||||
|
||||
// NewTickerWithTimer returns a new Ticker with a custom timer.
|
||||
// A default timer that uses system timer is used when nil is passed.
|
||||
func NewTickerWithTimer(b BackOff, timer Timer) *Ticker {
|
||||
if timer == nil {
|
||||
timer = &defaultTimer{}
|
||||
}
|
||||
c := make(chan time.Time)
|
||||
t := &Ticker{
|
||||
C: c,
|
||||
c: c,
|
||||
b: b,
|
||||
ctx: getContext(b),
|
||||
timer: timer,
|
||||
timer: &defaultTimer{},
|
||||
stop: make(chan struct{}),
|
||||
}
|
||||
t.b.Reset()
|
||||
|
|
@ -73,8 +61,6 @@ func (t *Ticker) run() {
|
|||
case <-t.stop:
|
||||
t.c = nil // Prevent future ticks from being sent to the channel.
|
||||
return
|
||||
case <-t.ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ package backoff
|
|||
|
||||
import "time"
|
||||
|
||||
type Timer interface {
|
||||
type timer interface {
|
||||
Start(duration time.Duration)
|
||||
Stop()
|
||||
C() <-chan time.Time
|
||||
28
vendor/github.com/grpc-ecosystem/grpc-gateway/v2/runtime/errors.go
generated
vendored
28
vendor/github.com/grpc-ecosystem/grpc-gateway/v2/runtime/errors.go
generated
vendored
|
|
@ -148,22 +148,20 @@ func DefaultHTTPErrorHandler(ctx context.Context, mux *ServeMux, marshaler Marsh
|
|||
}
|
||||
|
||||
md, ok := ServerMetadataFromContext(ctx)
|
||||
if !ok {
|
||||
grpclog.Error("Failed to extract ServerMetadata from context")
|
||||
}
|
||||
if ok {
|
||||
handleForwardResponseServerMetadata(w, mux, md)
|
||||
|
||||
handleForwardResponseServerMetadata(w, mux, md)
|
||||
// RFC 7230 https://tools.ietf.org/html/rfc7230#section-4.1.2
|
||||
// Unless the request includes a TE header field indicating "trailers"
|
||||
// is acceptable, as described in Section 4.3, a server SHOULD NOT
|
||||
// generate trailer fields that it believes are necessary for the user
|
||||
// agent to receive.
|
||||
doForwardTrailers := requestAcceptsTrailers(r)
|
||||
|
||||
// RFC 7230 https://tools.ietf.org/html/rfc7230#section-4.1.2
|
||||
// Unless the request includes a TE header field indicating "trailers"
|
||||
// is acceptable, as described in Section 4.3, a server SHOULD NOT
|
||||
// generate trailer fields that it believes are necessary for the user
|
||||
// agent to receive.
|
||||
doForwardTrailers := requestAcceptsTrailers(r)
|
||||
|
||||
if doForwardTrailers {
|
||||
handleForwardResponseTrailerHeader(w, mux, md)
|
||||
w.Header().Set("Transfer-Encoding", "chunked")
|
||||
if doForwardTrailers {
|
||||
handleForwardResponseTrailerHeader(w, mux, md)
|
||||
w.Header().Set("Transfer-Encoding", "chunked")
|
||||
}
|
||||
}
|
||||
|
||||
st := HTTPStatusFromCode(s.Code())
|
||||
|
|
@ -176,7 +174,7 @@ func DefaultHTTPErrorHandler(ctx context.Context, mux *ServeMux, marshaler Marsh
|
|||
grpclog.Errorf("Failed to write response: %v", err)
|
||||
}
|
||||
|
||||
if doForwardTrailers {
|
||||
if ok && requestAcceptsTrailers(r) {
|
||||
handleForwardResponseTrailer(w, mux, md)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
10
vendor/github.com/grpc-ecosystem/grpc-gateway/v2/runtime/handler.go
generated
vendored
10
vendor/github.com/grpc-ecosystem/grpc-gateway/v2/runtime/handler.go
generated
vendored
|
|
@ -153,12 +153,10 @@ type responseBody interface {
|
|||
// ForwardResponseMessage forwards the message "resp" from gRPC server to REST client.
|
||||
func ForwardResponseMessage(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.ResponseWriter, req *http.Request, resp proto.Message, opts ...func(context.Context, http.ResponseWriter, proto.Message) error) {
|
||||
md, ok := ServerMetadataFromContext(ctx)
|
||||
if !ok {
|
||||
grpclog.Error("Failed to extract ServerMetadata from context")
|
||||
if ok {
|
||||
handleForwardResponseServerMetadata(w, mux, md)
|
||||
}
|
||||
|
||||
handleForwardResponseServerMetadata(w, mux, md)
|
||||
|
||||
// RFC 7230 https://tools.ietf.org/html/rfc7230#section-4.1.2
|
||||
// Unless the request includes a TE header field indicating "trailers"
|
||||
// is acceptable, as described in Section 4.3, a server SHOULD NOT
|
||||
|
|
@ -166,7 +164,7 @@ func ForwardResponseMessage(ctx context.Context, mux *ServeMux, marshaler Marsha
|
|||
// agent to receive.
|
||||
doForwardTrailers := requestAcceptsTrailers(req)
|
||||
|
||||
if doForwardTrailers {
|
||||
if ok && doForwardTrailers {
|
||||
handleForwardResponseTrailerHeader(w, mux, md)
|
||||
w.Header().Set("Transfer-Encoding", "chunked")
|
||||
}
|
||||
|
|
@ -204,7 +202,7 @@ func ForwardResponseMessage(ctx context.Context, mux *ServeMux, marshaler Marsha
|
|||
grpclog.Errorf("Failed to write response: %v", err)
|
||||
}
|
||||
|
||||
if doForwardTrailers {
|
||||
if ok && doForwardTrailers {
|
||||
handleForwardResponseTrailer(w, mux, md)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
vendor/github.com/prometheus/common/expfmt/text_parse.go
generated
vendored
4
vendor/github.com/prometheus/common/expfmt/text_parse.go
generated
vendored
|
|
@ -345,8 +345,8 @@ func (p *TextParser) startLabelName() stateFn {
|
|||
}
|
||||
// Special summary/histogram treatment. Don't add 'quantile' and 'le'
|
||||
// labels to 'real' labels.
|
||||
if !(p.currentMF.GetType() == dto.MetricType_SUMMARY && p.currentLabelPair.GetName() == model.QuantileLabel) &&
|
||||
!(p.currentMF.GetType() == dto.MetricType_HISTOGRAM && p.currentLabelPair.GetName() == model.BucketLabel) {
|
||||
if (p.currentMF.GetType() != dto.MetricType_SUMMARY || p.currentLabelPair.GetName() != model.QuantileLabel) &&
|
||||
(p.currentMF.GetType() != dto.MetricType_HISTOGRAM || p.currentLabelPair.GetName() != model.BucketLabel) {
|
||||
p.currentLabelPairs = append(p.currentLabelPairs, p.currentLabelPair)
|
||||
}
|
||||
// Check for duplicate label names.
|
||||
|
|
|
|||
2
vendor/github.com/prometheus/common/model/alert.go
generated
vendored
2
vendor/github.com/prometheus/common/model/alert.go
generated
vendored
|
|
@ -65,7 +65,7 @@ func (a *Alert) Resolved() bool {
|
|||
return a.ResolvedAt(time.Now())
|
||||
}
|
||||
|
||||
// ResolvedAt returns true off the activity interval ended before
|
||||
// ResolvedAt returns true iff the activity interval ended before
|
||||
// the given timestamp.
|
||||
func (a *Alert) ResolvedAt(ts time.Time) bool {
|
||||
if a.EndsAt.IsZero() {
|
||||
|
|
|
|||
5
vendor/github.com/prometheus/common/model/labels.go
generated
vendored
5
vendor/github.com/prometheus/common/model/labels.go
generated
vendored
|
|
@ -22,7 +22,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
// AlertNameLabel is the name of the label containing the an alert's name.
|
||||
// AlertNameLabel is the name of the label containing the alert's name.
|
||||
AlertNameLabel = "alertname"
|
||||
|
||||
// ExportedLabelPrefix is the prefix to prepend to the label names present in
|
||||
|
|
@ -122,7 +122,8 @@ func (ln LabelName) IsValidLegacy() bool {
|
|||
return false
|
||||
}
|
||||
for i, b := range ln {
|
||||
if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || (b >= '0' && b <= '9' && i > 0)) {
|
||||
// TODO: Apply De Morgan's law. Make sure there are tests for this.
|
||||
if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || (b >= '0' && b <= '9' && i > 0)) { //nolint:staticcheck
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
28
vendor/github.com/prometheus/common/model/metric.go
generated
vendored
28
vendor/github.com/prometheus/common/model/metric.go
generated
vendored
|
|
@ -27,13 +27,25 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
// NameValidationScheme determines the method of name validation to be used by
|
||||
// all calls to IsValidMetricName() and LabelName IsValid(). Setting UTF-8
|
||||
// mode in isolation from other components that don't support UTF-8 may result
|
||||
// in bugs or other undefined behavior. This value can be set to
|
||||
// LegacyValidation during startup if a binary is not UTF-8-aware binaries. To
|
||||
// avoid need for locking, this value should be set once, ideally in an
|
||||
// init(), before multiple goroutines are started.
|
||||
// NameValidationScheme determines the global default method of the name
|
||||
// validation to be used by all calls to IsValidMetricName() and LabelName
|
||||
// IsValid().
|
||||
//
|
||||
// Deprecated: This variable should not be used and might be removed in the
|
||||
// far future. If you wish to stick to the legacy name validation use
|
||||
// `IsValidLegacyMetricName()` and `LabelName.IsValidLegacy()` methods
|
||||
// instead. This variable is here as an escape hatch for emergency cases,
|
||||
// given the recent change from `LegacyValidation` to `UTF8Validation`, e.g.,
|
||||
// to delay UTF-8 migrations in time or aid in debugging unforeseen results of
|
||||
// the change. In such a case, a temporary assignment to `LegacyValidation`
|
||||
// value in the `init()` function in your main.go or so, could be considered.
|
||||
//
|
||||
// Historically we opted for a global variable for feature gating different
|
||||
// validation schemes in operations that were not otherwise easily adjustable
|
||||
// (e.g. Labels yaml unmarshaling). That could have been a mistake, a separate
|
||||
// Labels structure or package might have been a better choice. Given the
|
||||
// change was made and many upgraded the common already, we live this as-is
|
||||
// with this warning and learning for the future.
|
||||
NameValidationScheme = UTF8Validation
|
||||
|
||||
// NameEscapingScheme defines the default way that names will be escaped when
|
||||
|
|
@ -50,7 +62,7 @@ var (
|
|||
type ValidationScheme int
|
||||
|
||||
const (
|
||||
// LegacyValidation is a setting that requirets that metric and label names
|
||||
// LegacyValidation is a setting that requires that all metric and label names
|
||||
// conform to the original Prometheus character requirements described by
|
||||
// MetricNameRE and LabelNameRE.
|
||||
LegacyValidation ValidationScheme = iota
|
||||
|
|
|
|||
63
vendor/github.com/prometheus/procfs/.golangci.yml
generated
vendored
63
vendor/github.com/prometheus/procfs/.golangci.yml
generated
vendored
|
|
@ -1,22 +1,45 @@
|
|||
---
|
||||
version: "2"
|
||||
linters:
|
||||
enable:
|
||||
- errcheck
|
||||
- godot
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- misspell
|
||||
- revive
|
||||
- staticcheck
|
||||
- testifylint
|
||||
- unused
|
||||
|
||||
linter-settings:
|
||||
godot:
|
||||
capital: true
|
||||
exclude:
|
||||
# Ignore "See: URL"
|
||||
- 'See:'
|
||||
misspell:
|
||||
locale: US
|
||||
- forbidigo
|
||||
- godot
|
||||
- misspell
|
||||
- revive
|
||||
- testifylint
|
||||
settings:
|
||||
forbidigo:
|
||||
forbid:
|
||||
- pattern: ^fmt\.Print.*$
|
||||
msg: Do not commit print statements.
|
||||
godot:
|
||||
exclude:
|
||||
# Ignore "See: URL".
|
||||
- 'See:'
|
||||
capital: true
|
||||
misspell:
|
||||
locale: US
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
formatters:
|
||||
enable:
|
||||
- gofmt
|
||||
- goimports
|
||||
settings:
|
||||
goimports:
|
||||
local-prefixes:
|
||||
- github.com/prometheus/procfs
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
|
|
|
|||
10
vendor/github.com/prometheus/procfs/Makefile.common
generated
vendored
10
vendor/github.com/prometheus/procfs/Makefile.common
generated
vendored
|
|
@ -33,7 +33,7 @@ GOHOSTOS ?= $(shell $(GO) env GOHOSTOS)
|
|||
GOHOSTARCH ?= $(shell $(GO) env GOHOSTARCH)
|
||||
|
||||
GO_VERSION ?= $(shell $(GO) version)
|
||||
GO_VERSION_NUMBER ?= $(word 3, $(GO_VERSION))
|
||||
GO_VERSION_NUMBER ?= $(word 3, $(GO_VERSION))Error Parsing File
|
||||
PRE_GO_111 ?= $(shell echo $(GO_VERSION_NUMBER) | grep -E 'go1\.(10|[0-9])\.')
|
||||
|
||||
PROMU := $(FIRST_GOPATH)/bin/promu
|
||||
|
|
@ -61,7 +61,7 @@ PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_
|
|||
SKIP_GOLANGCI_LINT :=
|
||||
GOLANGCI_LINT :=
|
||||
GOLANGCI_LINT_OPTS ?=
|
||||
GOLANGCI_LINT_VERSION ?= v1.59.0
|
||||
GOLANGCI_LINT_VERSION ?= v2.0.2
|
||||
# golangci-lint only supports linux, darwin and windows platforms on i386/amd64/arm64.
|
||||
# windows isn't included here because of the path separator being different.
|
||||
ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin))
|
||||
|
|
@ -275,3 +275,9 @@ $(1)_precheck:
|
|||
exit 1; \
|
||||
fi
|
||||
endef
|
||||
|
||||
govulncheck: install-govulncheck
|
||||
govulncheck ./...
|
||||
|
||||
install-govulncheck:
|
||||
command -v govulncheck > /dev/null || go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||
|
|
|
|||
6
vendor/github.com/prometheus/procfs/README.md
generated
vendored
6
vendor/github.com/prometheus/procfs/README.md
generated
vendored
|
|
@ -47,15 +47,15 @@ However, most of the API includes unit tests which can be run with `make test`.
|
|||
The procfs library includes a set of test fixtures which include many example files from
|
||||
the `/proc` and `/sys` filesystems. These fixtures are included as a [ttar](https://github.com/ideaship/ttar) file
|
||||
which is extracted automatically during testing. To add/update the test fixtures, first
|
||||
ensure the `fixtures` directory is up to date by removing the existing directory and then
|
||||
extracting the ttar file using `make fixtures/.unpacked` or just `make test`.
|
||||
ensure the `testdata/fixtures` directory is up to date by removing the existing directory and then
|
||||
extracting the ttar file using `make testdata/fixtures/.unpacked` or just `make test`.
|
||||
|
||||
```bash
|
||||
rm -rf testdata/fixtures
|
||||
make test
|
||||
```
|
||||
|
||||
Next, make the required changes to the extracted files in the `fixtures` directory. When
|
||||
Next, make the required changes to the extracted files in the `testdata/fixtures` directory. When
|
||||
the changes are complete, run `make update_fixtures` to create a new `fixtures.ttar` file
|
||||
based on the updated `fixtures` directory. And finally, verify the changes using
|
||||
`git diff testdata/fixtures.ttar`.
|
||||
|
|
|
|||
4
vendor/github.com/prometheus/procfs/arp.go
generated
vendored
4
vendor/github.com/prometheus/procfs/arp.go
generated
vendored
|
|
@ -23,9 +23,9 @@ import (
|
|||
|
||||
// Learned from include/uapi/linux/if_arp.h.
|
||||
const (
|
||||
// completed entry (ha valid).
|
||||
// Completed entry (ha valid).
|
||||
ATFComplete = 0x02
|
||||
// permanent entry.
|
||||
// Permanent entry.
|
||||
ATFPermanent = 0x04
|
||||
// Publish entry.
|
||||
ATFPublish = 0x08
|
||||
|
|
|
|||
10
vendor/github.com/prometheus/procfs/fs.go
generated
vendored
10
vendor/github.com/prometheus/procfs/fs.go
generated
vendored
|
|
@ -24,8 +24,14 @@ type FS struct {
|
|||
isReal bool
|
||||
}
|
||||
|
||||
// DefaultMountPoint is the common mount point of the proc filesystem.
|
||||
const DefaultMountPoint = fs.DefaultProcMountPoint
|
||||
const (
|
||||
// DefaultMountPoint is the common mount point of the proc filesystem.
|
||||
DefaultMountPoint = fs.DefaultProcMountPoint
|
||||
|
||||
// SectorSize represents the size of a sector in bytes.
|
||||
// It is specific to Linux block I/O operations.
|
||||
SectorSize = 512
|
||||
)
|
||||
|
||||
// NewDefaultFS returns a new proc FS mounted under the default proc mountPoint.
|
||||
// It will error if the mount point directory can't be read or is a file.
|
||||
|
|
|
|||
4
vendor/github.com/prometheus/procfs/fs_statfs_notype.go
generated
vendored
4
vendor/github.com/prometheus/procfs/fs_statfs_notype.go
generated
vendored
|
|
@ -17,7 +17,7 @@
|
|||
package procfs
|
||||
|
||||
// isRealProc returns true on architectures that don't have a Type argument
|
||||
// in their Statfs_t struct
|
||||
func isRealProc(mountPoint string) (bool, error) {
|
||||
// in their Statfs_t struct.
|
||||
func isRealProc(_ string) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
|
|
|||
6
vendor/github.com/prometheus/procfs/fscache.go
generated
vendored
6
vendor/github.com/prometheus/procfs/fscache.go
generated
vendored
|
|
@ -162,7 +162,7 @@ type Fscacheinfo struct {
|
|||
ReleaseRequestsAgainstPagesStoredByTimeLockGranted uint64
|
||||
// Number of release reqs ignored due to in-progress store
|
||||
ReleaseRequestsIgnoredDueToInProgressStore uint64
|
||||
// Number of page stores cancelled due to release req
|
||||
// Number of page stores canceled due to release req
|
||||
PageStoresCancelledByReleaseRequests uint64
|
||||
VmscanWaiting uint64
|
||||
// Number of times async ops added to pending queues
|
||||
|
|
@ -171,11 +171,11 @@ type Fscacheinfo struct {
|
|||
OpsRunning uint64
|
||||
// Number of times async ops queued for processing
|
||||
OpsEnqueued uint64
|
||||
// Number of async ops cancelled
|
||||
// Number of async ops canceled
|
||||
OpsCancelled uint64
|
||||
// Number of async ops rejected due to object lookup/create failure
|
||||
OpsRejected uint64
|
||||
// Number of async ops initialised
|
||||
// Number of async ops initialized
|
||||
OpsInitialised uint64
|
||||
// Number of async ops queued for deferred release
|
||||
OpsDeferred uint64
|
||||
|
|
|
|||
3
vendor/github.com/prometheus/procfs/internal/fs/fs.go
generated
vendored
3
vendor/github.com/prometheus/procfs/internal/fs/fs.go
generated
vendored
|
|
@ -28,6 +28,9 @@ const (
|
|||
|
||||
// DefaultConfigfsMountPoint is the common mount point of the configfs.
|
||||
DefaultConfigfsMountPoint = "/sys/kernel/config"
|
||||
|
||||
// DefaultSelinuxMountPoint is the common mount point of the selinuxfs.
|
||||
DefaultSelinuxMountPoint = "/sys/fs/selinux"
|
||||
)
|
||||
|
||||
// FS represents a pseudo-filesystem, normally /proc or /sys, which provides an
|
||||
|
|
|
|||
14
vendor/github.com/prometheus/procfs/internal/util/parse.go
generated
vendored
14
vendor/github.com/prometheus/procfs/internal/util/parse.go
generated
vendored
|
|
@ -14,6 +14,7 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -110,3 +111,16 @@ func ParseBool(b string) *bool {
|
|||
}
|
||||
return &truth
|
||||
}
|
||||
|
||||
// ReadHexFromFile reads a file and attempts to parse a uint64 from a hexadecimal format 0xXX.
|
||||
func ReadHexFromFile(path string) (uint64, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
hexString := strings.TrimSpace(string(data))
|
||||
if !strings.HasPrefix(hexString, "0x") {
|
||||
return 0, errors.New("invalid format: hex string does not start with '0x'")
|
||||
}
|
||||
return strconv.ParseUint(hexString[2:], 16, 64)
|
||||
}
|
||||
|
|
|
|||
20
vendor/github.com/prometheus/procfs/internal/util/sysreadfile.go
generated
vendored
20
vendor/github.com/prometheus/procfs/internal/util/sysreadfile.go
generated
vendored
|
|
@ -20,6 +20,8 @@ package util
|
|||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
|
|
@ -48,3 +50,21 @@ func SysReadFile(file string) (string, error) {
|
|||
|
||||
return string(bytes.TrimSpace(b[:n])), nil
|
||||
}
|
||||
|
||||
// SysReadUintFromFile reads a file using SysReadFile and attempts to parse a uint64 from it.
|
||||
func SysReadUintFromFile(path string) (uint64, error) {
|
||||
data, err := SysReadFile(path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return strconv.ParseUint(strings.TrimSpace(string(data)), 10, 64)
|
||||
}
|
||||
|
||||
// SysReadIntFromFile reads a file using SysReadFile and attempts to parse a int64 from it.
|
||||
func SysReadIntFromFile(path string) (int64, error) {
|
||||
data, err := SysReadFile(path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return strconv.ParseInt(strings.TrimSpace(string(data)), 10, 64)
|
||||
}
|
||||
|
|
|
|||
27
vendor/github.com/prometheus/procfs/mountstats.go
generated
vendored
27
vendor/github.com/prometheus/procfs/mountstats.go
generated
vendored
|
|
@ -45,11 +45,11 @@ const (
|
|||
fieldTransport11TCPLen = 13
|
||||
fieldTransport11UDPLen = 10
|
||||
|
||||
// kernel version >= 4.14 MaxLen
|
||||
// Kernel version >= 4.14 MaxLen
|
||||
// See: https://elixir.bootlin.com/linux/v6.4.8/source/net/sunrpc/xprtrdma/xprt_rdma.h#L393
|
||||
fieldTransport11RDMAMaxLen = 28
|
||||
|
||||
// kernel version <= 4.2 MinLen
|
||||
// Kernel version <= 4.2 MinLen
|
||||
// See: https://elixir.bootlin.com/linux/v4.2.8/source/net/sunrpc/xprtrdma/xprt_rdma.h#L331
|
||||
fieldTransport11RDMAMinLen = 20
|
||||
)
|
||||
|
|
@ -601,11 +601,12 @@ func parseNFSTransportStats(ss []string, statVersion string) (*NFSTransportStats
|
|||
switch statVersion {
|
||||
case statVersion10:
|
||||
var expectedLength int
|
||||
if protocol == "tcp" {
|
||||
switch protocol {
|
||||
case "tcp":
|
||||
expectedLength = fieldTransport10TCPLen
|
||||
} else if protocol == "udp" {
|
||||
case "udp":
|
||||
expectedLength = fieldTransport10UDPLen
|
||||
} else {
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: Invalid NFS protocol \"%s\" in stats 1.0 statement: %v", ErrFileParse, protocol, ss)
|
||||
}
|
||||
if len(ss) != expectedLength {
|
||||
|
|
@ -613,13 +614,14 @@ func parseNFSTransportStats(ss []string, statVersion string) (*NFSTransportStats
|
|||
}
|
||||
case statVersion11:
|
||||
var expectedLength int
|
||||
if protocol == "tcp" {
|
||||
switch protocol {
|
||||
case "tcp":
|
||||
expectedLength = fieldTransport11TCPLen
|
||||
} else if protocol == "udp" {
|
||||
case "udp":
|
||||
expectedLength = fieldTransport11UDPLen
|
||||
} else if protocol == "rdma" {
|
||||
case "rdma":
|
||||
expectedLength = fieldTransport11RDMAMinLen
|
||||
} else {
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: invalid NFS protocol \"%s\" in stats 1.1 statement: %v", ErrFileParse, protocol, ss)
|
||||
}
|
||||
if (len(ss) != expectedLength && (protocol == "tcp" || protocol == "udp")) ||
|
||||
|
|
@ -655,11 +657,12 @@ func parseNFSTransportStats(ss []string, statVersion string) (*NFSTransportStats
|
|||
// For the udp RPC transport there is no connection count, connect idle time,
|
||||
// or idle time (fields #3, #4, and #5); all other fields are the same. So
|
||||
// we set them to 0 here.
|
||||
if protocol == "udp" {
|
||||
switch protocol {
|
||||
case "udp":
|
||||
ns = append(ns[:2], append(make([]uint64, 3), ns[2:]...)...)
|
||||
} else if protocol == "tcp" {
|
||||
case "tcp":
|
||||
ns = append(ns[:fieldTransport11TCPLen], make([]uint64, fieldTransport11RDMAMaxLen-fieldTransport11TCPLen+3)...)
|
||||
} else if protocol == "rdma" {
|
||||
case "rdma":
|
||||
ns = append(ns[:fieldTransport10TCPLen], append(make([]uint64, 3), ns[fieldTransport10TCPLen:]...)...)
|
||||
}
|
||||
|
||||
|
|
|
|||
96
vendor/github.com/prometheus/procfs/net_dev_snmp6.go
generated
vendored
Normal file
96
vendor/github.com/prometheus/procfs/net_dev_snmp6.go
generated
vendored
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
// Copyright 2018 The Prometheus Authors
|
||||
// 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
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package procfs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NetDevSNMP6 is parsed from files in /proc/net/dev_snmp6/ or /proc/<PID>/net/dev_snmp6/.
|
||||
// The outer map's keys are interface names and the inner map's keys are stat names.
|
||||
//
|
||||
// If you'd like a total across all interfaces, please use the Snmp6() method of the Proc type.
|
||||
type NetDevSNMP6 map[string]map[string]uint64
|
||||
|
||||
// Returns kernel/system statistics read from interface files within the /proc/net/dev_snmp6/
|
||||
// directory.
|
||||
func (fs FS) NetDevSNMP6() (NetDevSNMP6, error) {
|
||||
return newNetDevSNMP6(fs.proc.Path("net/dev_snmp6"))
|
||||
}
|
||||
|
||||
// Returns kernel/system statistics read from interface files within the /proc/<PID>/net/dev_snmp6/
|
||||
// directory.
|
||||
func (p Proc) NetDevSNMP6() (NetDevSNMP6, error) {
|
||||
return newNetDevSNMP6(p.path("net/dev_snmp6"))
|
||||
}
|
||||
|
||||
// newNetDevSNMP6 creates a new NetDevSNMP6 from the contents of the given directory.
|
||||
func newNetDevSNMP6(dir string) (NetDevSNMP6, error) {
|
||||
netDevSNMP6 := make(NetDevSNMP6)
|
||||
|
||||
// The net/dev_snmp6 folders contain one file per interface
|
||||
ifaceFiles, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
// On systems with IPv6 disabled, this directory won't exist.
|
||||
// Do nothing.
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return netDevSNMP6, err
|
||||
}
|
||||
return netDevSNMP6, err
|
||||
}
|
||||
|
||||
for _, iFaceFile := range ifaceFiles {
|
||||
f, err := os.Open(dir + "/" + iFaceFile.Name())
|
||||
if err != nil {
|
||||
return netDevSNMP6, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
netDevSNMP6[iFaceFile.Name()], err = parseNetDevSNMP6Stats(f)
|
||||
if err != nil {
|
||||
return netDevSNMP6, err
|
||||
}
|
||||
}
|
||||
|
||||
return netDevSNMP6, nil
|
||||
}
|
||||
|
||||
func parseNetDevSNMP6Stats(r io.Reader) (map[string]uint64, error) {
|
||||
m := make(map[string]uint64)
|
||||
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
stat := strings.Fields(scanner.Text())
|
||||
if len(stat) < 2 {
|
||||
continue
|
||||
}
|
||||
key, val := stat[0], stat[1]
|
||||
|
||||
// Expect stat name to contain "6" or be "ifIndex"
|
||||
if strings.Contains(key, "6") || key == "ifIndex" {
|
||||
v, err := strconv.ParseUint(val, 10, 64)
|
||||
if err != nil {
|
||||
return m, err
|
||||
}
|
||||
|
||||
m[key] = v
|
||||
}
|
||||
}
|
||||
return m, scanner.Err()
|
||||
}
|
||||
8
vendor/github.com/prometheus/procfs/net_ip_socket.go
generated
vendored
8
vendor/github.com/prometheus/procfs/net_ip_socket.go
generated
vendored
|
|
@ -25,7 +25,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
// readLimit is used by io.LimitReader while reading the content of the
|
||||
// Maximum size limit used by io.LimitReader while reading the content of the
|
||||
// /proc/net/udp{,6} files. The number of lines inside such a file is dynamic
|
||||
// as each line represents a single used socket.
|
||||
// In theory, the number of available sockets is 65535 (2^16 - 1) per IP.
|
||||
|
|
@ -50,12 +50,12 @@ type (
|
|||
// UsedSockets shows the total number of parsed lines representing the
|
||||
// number of used sockets.
|
||||
UsedSockets uint64
|
||||
// Drops shows the total number of dropped packets of all UPD sockets.
|
||||
// Drops shows the total number of dropped packets of all UDP sockets.
|
||||
Drops *uint64
|
||||
}
|
||||
|
||||
// netIPSocketLine represents the fields parsed from a single line
|
||||
// in /proc/net/{t,u}dp{,6}. Fields which are not used by IPSocket are skipped.
|
||||
// A single line parser for fields from /proc/net/{t,u}dp{,6}.
|
||||
// Fields which are not used by IPSocket are skipped.
|
||||
// Drops is non-nil for udp{,6}, but nil for tcp{,6}.
|
||||
// For the proc file format details, see https://linux.die.net/man/5/proc.
|
||||
netIPSocketLine struct {
|
||||
|
|
|
|||
21
vendor/github.com/prometheus/procfs/net_protocols.go
generated
vendored
21
vendor/github.com/prometheus/procfs/net_protocols.go
generated
vendored
|
|
@ -115,22 +115,24 @@ func (ps NetProtocolStats) parseLine(rawLine string) (*NetProtocolStatLine, erro
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if fields[4] == enabled {
|
||||
switch fields[4] {
|
||||
case enabled:
|
||||
line.Pressure = 1
|
||||
} else if fields[4] == disabled {
|
||||
case disabled:
|
||||
line.Pressure = 0
|
||||
} else {
|
||||
default:
|
||||
line.Pressure = -1
|
||||
}
|
||||
line.MaxHeader, err = strconv.ParseUint(fields[5], 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if fields[6] == enabled {
|
||||
switch fields[6] {
|
||||
case enabled:
|
||||
line.Slab = true
|
||||
} else if fields[6] == disabled {
|
||||
case disabled:
|
||||
line.Slab = false
|
||||
} else {
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: capability for protocol: %s", ErrFileParse, line.Name)
|
||||
}
|
||||
line.ModuleName = fields[7]
|
||||
|
|
@ -168,11 +170,12 @@ func (pc *NetProtocolCapabilities) parseCapabilities(capabilities []string) erro
|
|||
}
|
||||
|
||||
for i := 0; i < len(capabilities); i++ {
|
||||
if capabilities[i] == "y" {
|
||||
switch capabilities[i] {
|
||||
case "y":
|
||||
*capabilityFields[i] = true
|
||||
} else if capabilities[i] == "n" {
|
||||
case "n":
|
||||
*capabilityFields[i] = false
|
||||
} else {
|
||||
default:
|
||||
return fmt.Errorf("%w: capability block for protocol: position %d", ErrFileParse, i)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
vendor/github.com/prometheus/procfs/net_tcp.go
generated
vendored
4
vendor/github.com/prometheus/procfs/net_tcp.go
generated
vendored
|
|
@ -25,24 +25,28 @@ type (
|
|||
|
||||
// NetTCP returns the IPv4 kernel/networking statistics for TCP datagrams
|
||||
// read from /proc/net/tcp.
|
||||
// Deprecated: Use github.com/mdlayher/netlink#Conn (with syscall.AF_INET) instead.
|
||||
func (fs FS) NetTCP() (NetTCP, error) {
|
||||
return newNetTCP(fs.proc.Path("net/tcp"))
|
||||
}
|
||||
|
||||
// NetTCP6 returns the IPv6 kernel/networking statistics for TCP datagrams
|
||||
// read from /proc/net/tcp6.
|
||||
// Deprecated: Use github.com/mdlayher/netlink#Conn (with syscall.AF_INET6) instead.
|
||||
func (fs FS) NetTCP6() (NetTCP, error) {
|
||||
return newNetTCP(fs.proc.Path("net/tcp6"))
|
||||
}
|
||||
|
||||
// NetTCPSummary returns already computed statistics like the total queue lengths
|
||||
// for TCP datagrams read from /proc/net/tcp.
|
||||
// Deprecated: Use github.com/mdlayher/netlink#Conn (with syscall.AF_INET) instead.
|
||||
func (fs FS) NetTCPSummary() (*NetTCPSummary, error) {
|
||||
return newNetTCPSummary(fs.proc.Path("net/tcp"))
|
||||
}
|
||||
|
||||
// NetTCP6Summary returns already computed statistics like the total queue lengths
|
||||
// for TCP datagrams read from /proc/net/tcp6.
|
||||
// Deprecated: Use github.com/mdlayher/netlink#Conn (with syscall.AF_INET6) instead.
|
||||
func (fs FS) NetTCP6Summary() (*NetTCPSummary, error) {
|
||||
return newNetTCPSummary(fs.proc.Path("net/tcp6"))
|
||||
}
|
||||
|
|
|
|||
8
vendor/github.com/prometheus/procfs/net_unix.go
generated
vendored
8
vendor/github.com/prometheus/procfs/net_unix.go
generated
vendored
|
|
@ -121,12 +121,12 @@ func parseNetUNIX(r io.Reader) (*NetUNIX, error) {
|
|||
return &nu, nil
|
||||
}
|
||||
|
||||
func (u *NetUNIX) parseLine(line string, hasInode bool, min int) (*NetUNIXLine, error) {
|
||||
func (u *NetUNIX) parseLine(line string, hasInode bool, minFields int) (*NetUNIXLine, error) {
|
||||
fields := strings.Fields(line)
|
||||
|
||||
l := len(fields)
|
||||
if l < min {
|
||||
return nil, fmt.Errorf("%w: expected at least %d fields but got %d", ErrFileParse, min, l)
|
||||
if l < minFields {
|
||||
return nil, fmt.Errorf("%w: expected at least %d fields but got %d", ErrFileParse, minFields, l)
|
||||
}
|
||||
|
||||
// Field offsets are as follows:
|
||||
|
|
@ -172,7 +172,7 @@ func (u *NetUNIX) parseLine(line string, hasInode bool, min int) (*NetUNIXLine,
|
|||
}
|
||||
|
||||
// Path field is optional.
|
||||
if l > min {
|
||||
if l > minFields {
|
||||
// Path occurs at either index 6 or 7 depending on whether inode is
|
||||
// already present.
|
||||
pathIdx := 7
|
||||
|
|
|
|||
8
vendor/github.com/prometheus/procfs/proc.go
generated
vendored
8
vendor/github.com/prometheus/procfs/proc.go
generated
vendored
|
|
@ -37,9 +37,9 @@ type Proc struct {
|
|||
type Procs []Proc
|
||||
|
||||
var (
|
||||
ErrFileParse = errors.New("Error Parsing File")
|
||||
ErrFileRead = errors.New("Error Reading File")
|
||||
ErrMountPoint = errors.New("Error Accessing Mount point")
|
||||
ErrFileParse = errors.New("error parsing file")
|
||||
ErrFileRead = errors.New("error reading file")
|
||||
ErrMountPoint = errors.New("error accessing mount point")
|
||||
)
|
||||
|
||||
func (p Procs) Len() int { return len(p) }
|
||||
|
|
@ -79,7 +79,7 @@ func (fs FS) Self() (Proc, error) {
|
|||
if err != nil {
|
||||
return Proc{}, err
|
||||
}
|
||||
pid, err := strconv.Atoi(strings.Replace(p, string(fs.proc), "", -1))
|
||||
pid, err := strconv.Atoi(strings.ReplaceAll(p, string(fs.proc), ""))
|
||||
if err != nil {
|
||||
return Proc{}, err
|
||||
}
|
||||
|
|
|
|||
2
vendor/github.com/prometheus/procfs/proc_cgroup.go
generated
vendored
2
vendor/github.com/prometheus/procfs/proc_cgroup.go
generated
vendored
|
|
@ -24,7 +24,7 @@ import (
|
|||
)
|
||||
|
||||
// Cgroup models one line from /proc/[pid]/cgroup. Each Cgroup struct describes the placement of a PID inside a
|
||||
// specific control hierarchy. The kernel has two cgroup APIs, v1 and v2. v1 has one hierarchy per available resource
|
||||
// specific control hierarchy. The kernel has two cgroup APIs, v1 and v2. The v1 has one hierarchy per available resource
|
||||
// controller, while v2 has one unified hierarchy shared by all controllers. Regardless of v1 or v2, all hierarchies
|
||||
// contain all running processes, so the question answerable with a Cgroup struct is 'where is this process in
|
||||
// this hierarchy' (where==what path on the specific cgroupfs). By prefixing this path with the mount point of
|
||||
|
|
|
|||
2
vendor/github.com/prometheus/procfs/proc_io.go
generated
vendored
2
vendor/github.com/prometheus/procfs/proc_io.go
generated
vendored
|
|
@ -50,7 +50,7 @@ func (p Proc) IO() (ProcIO, error) {
|
|||
|
||||
ioFormat := "rchar: %d\nwchar: %d\nsyscr: %d\nsyscw: %d\n" +
|
||||
"read_bytes: %d\nwrite_bytes: %d\n" +
|
||||
"cancelled_write_bytes: %d\n"
|
||||
"cancelled_write_bytes: %d\n" //nolint:misspell
|
||||
|
||||
_, err = fmt.Sscanf(string(data), ioFormat, &pio.RChar, &pio.WChar, &pio.SyscR,
|
||||
&pio.SyscW, &pio.ReadBytes, &pio.WriteBytes, &pio.CancelledWriteBytes)
|
||||
|
|
|
|||
224
vendor/github.com/prometheus/procfs/proc_netstat.go
generated
vendored
224
vendor/github.com/prometheus/procfs/proc_netstat.go
generated
vendored
|
|
@ -209,232 +209,232 @@ func parseProcNetstat(r io.Reader, fileName string) (ProcNetstat, error) {
|
|||
case "TcpExt":
|
||||
switch key {
|
||||
case "SyncookiesSent":
|
||||
procNetstat.TcpExt.SyncookiesSent = &value
|
||||
procNetstat.SyncookiesSent = &value
|
||||
case "SyncookiesRecv":
|
||||
procNetstat.TcpExt.SyncookiesRecv = &value
|
||||
procNetstat.SyncookiesRecv = &value
|
||||
case "SyncookiesFailed":
|
||||
procNetstat.TcpExt.SyncookiesFailed = &value
|
||||
procNetstat.SyncookiesFailed = &value
|
||||
case "EmbryonicRsts":
|
||||
procNetstat.TcpExt.EmbryonicRsts = &value
|
||||
procNetstat.EmbryonicRsts = &value
|
||||
case "PruneCalled":
|
||||
procNetstat.TcpExt.PruneCalled = &value
|
||||
procNetstat.PruneCalled = &value
|
||||
case "RcvPruned":
|
||||
procNetstat.TcpExt.RcvPruned = &value
|
||||
procNetstat.RcvPruned = &value
|
||||
case "OfoPruned":
|
||||
procNetstat.TcpExt.OfoPruned = &value
|
||||
procNetstat.OfoPruned = &value
|
||||
case "OutOfWindowIcmps":
|
||||
procNetstat.TcpExt.OutOfWindowIcmps = &value
|
||||
procNetstat.OutOfWindowIcmps = &value
|
||||
case "LockDroppedIcmps":
|
||||
procNetstat.TcpExt.LockDroppedIcmps = &value
|
||||
procNetstat.LockDroppedIcmps = &value
|
||||
case "ArpFilter":
|
||||
procNetstat.TcpExt.ArpFilter = &value
|
||||
procNetstat.ArpFilter = &value
|
||||
case "TW":
|
||||
procNetstat.TcpExt.TW = &value
|
||||
procNetstat.TW = &value
|
||||
case "TWRecycled":
|
||||
procNetstat.TcpExt.TWRecycled = &value
|
||||
procNetstat.TWRecycled = &value
|
||||
case "TWKilled":
|
||||
procNetstat.TcpExt.TWKilled = &value
|
||||
procNetstat.TWKilled = &value
|
||||
case "PAWSActive":
|
||||
procNetstat.TcpExt.PAWSActive = &value
|
||||
procNetstat.PAWSActive = &value
|
||||
case "PAWSEstab":
|
||||
procNetstat.TcpExt.PAWSEstab = &value
|
||||
procNetstat.PAWSEstab = &value
|
||||
case "DelayedACKs":
|
||||
procNetstat.TcpExt.DelayedACKs = &value
|
||||
procNetstat.DelayedACKs = &value
|
||||
case "DelayedACKLocked":
|
||||
procNetstat.TcpExt.DelayedACKLocked = &value
|
||||
procNetstat.DelayedACKLocked = &value
|
||||
case "DelayedACKLost":
|
||||
procNetstat.TcpExt.DelayedACKLost = &value
|
||||
procNetstat.DelayedACKLost = &value
|
||||
case "ListenOverflows":
|
||||
procNetstat.TcpExt.ListenOverflows = &value
|
||||
procNetstat.ListenOverflows = &value
|
||||
case "ListenDrops":
|
||||
procNetstat.TcpExt.ListenDrops = &value
|
||||
procNetstat.ListenDrops = &value
|
||||
case "TCPHPHits":
|
||||
procNetstat.TcpExt.TCPHPHits = &value
|
||||
procNetstat.TCPHPHits = &value
|
||||
case "TCPPureAcks":
|
||||
procNetstat.TcpExt.TCPPureAcks = &value
|
||||
procNetstat.TCPPureAcks = &value
|
||||
case "TCPHPAcks":
|
||||
procNetstat.TcpExt.TCPHPAcks = &value
|
||||
procNetstat.TCPHPAcks = &value
|
||||
case "TCPRenoRecovery":
|
||||
procNetstat.TcpExt.TCPRenoRecovery = &value
|
||||
procNetstat.TCPRenoRecovery = &value
|
||||
case "TCPSackRecovery":
|
||||
procNetstat.TcpExt.TCPSackRecovery = &value
|
||||
procNetstat.TCPSackRecovery = &value
|
||||
case "TCPSACKReneging":
|
||||
procNetstat.TcpExt.TCPSACKReneging = &value
|
||||
procNetstat.TCPSACKReneging = &value
|
||||
case "TCPSACKReorder":
|
||||
procNetstat.TcpExt.TCPSACKReorder = &value
|
||||
procNetstat.TCPSACKReorder = &value
|
||||
case "TCPRenoReorder":
|
||||
procNetstat.TcpExt.TCPRenoReorder = &value
|
||||
procNetstat.TCPRenoReorder = &value
|
||||
case "TCPTSReorder":
|
||||
procNetstat.TcpExt.TCPTSReorder = &value
|
||||
procNetstat.TCPTSReorder = &value
|
||||
case "TCPFullUndo":
|
||||
procNetstat.TcpExt.TCPFullUndo = &value
|
||||
procNetstat.TCPFullUndo = &value
|
||||
case "TCPPartialUndo":
|
||||
procNetstat.TcpExt.TCPPartialUndo = &value
|
||||
procNetstat.TCPPartialUndo = &value
|
||||
case "TCPDSACKUndo":
|
||||
procNetstat.TcpExt.TCPDSACKUndo = &value
|
||||
procNetstat.TCPDSACKUndo = &value
|
||||
case "TCPLossUndo":
|
||||
procNetstat.TcpExt.TCPLossUndo = &value
|
||||
procNetstat.TCPLossUndo = &value
|
||||
case "TCPLostRetransmit":
|
||||
procNetstat.TcpExt.TCPLostRetransmit = &value
|
||||
procNetstat.TCPLostRetransmit = &value
|
||||
case "TCPRenoFailures":
|
||||
procNetstat.TcpExt.TCPRenoFailures = &value
|
||||
procNetstat.TCPRenoFailures = &value
|
||||
case "TCPSackFailures":
|
||||
procNetstat.TcpExt.TCPSackFailures = &value
|
||||
procNetstat.TCPSackFailures = &value
|
||||
case "TCPLossFailures":
|
||||
procNetstat.TcpExt.TCPLossFailures = &value
|
||||
procNetstat.TCPLossFailures = &value
|
||||
case "TCPFastRetrans":
|
||||
procNetstat.TcpExt.TCPFastRetrans = &value
|
||||
procNetstat.TCPFastRetrans = &value
|
||||
case "TCPSlowStartRetrans":
|
||||
procNetstat.TcpExt.TCPSlowStartRetrans = &value
|
||||
procNetstat.TCPSlowStartRetrans = &value
|
||||
case "TCPTimeouts":
|
||||
procNetstat.TcpExt.TCPTimeouts = &value
|
||||
procNetstat.TCPTimeouts = &value
|
||||
case "TCPLossProbes":
|
||||
procNetstat.TcpExt.TCPLossProbes = &value
|
||||
procNetstat.TCPLossProbes = &value
|
||||
case "TCPLossProbeRecovery":
|
||||
procNetstat.TcpExt.TCPLossProbeRecovery = &value
|
||||
procNetstat.TCPLossProbeRecovery = &value
|
||||
case "TCPRenoRecoveryFail":
|
||||
procNetstat.TcpExt.TCPRenoRecoveryFail = &value
|
||||
procNetstat.TCPRenoRecoveryFail = &value
|
||||
case "TCPSackRecoveryFail":
|
||||
procNetstat.TcpExt.TCPSackRecoveryFail = &value
|
||||
procNetstat.TCPSackRecoveryFail = &value
|
||||
case "TCPRcvCollapsed":
|
||||
procNetstat.TcpExt.TCPRcvCollapsed = &value
|
||||
procNetstat.TCPRcvCollapsed = &value
|
||||
case "TCPDSACKOldSent":
|
||||
procNetstat.TcpExt.TCPDSACKOldSent = &value
|
||||
procNetstat.TCPDSACKOldSent = &value
|
||||
case "TCPDSACKOfoSent":
|
||||
procNetstat.TcpExt.TCPDSACKOfoSent = &value
|
||||
procNetstat.TCPDSACKOfoSent = &value
|
||||
case "TCPDSACKRecv":
|
||||
procNetstat.TcpExt.TCPDSACKRecv = &value
|
||||
procNetstat.TCPDSACKRecv = &value
|
||||
case "TCPDSACKOfoRecv":
|
||||
procNetstat.TcpExt.TCPDSACKOfoRecv = &value
|
||||
procNetstat.TCPDSACKOfoRecv = &value
|
||||
case "TCPAbortOnData":
|
||||
procNetstat.TcpExt.TCPAbortOnData = &value
|
||||
procNetstat.TCPAbortOnData = &value
|
||||
case "TCPAbortOnClose":
|
||||
procNetstat.TcpExt.TCPAbortOnClose = &value
|
||||
procNetstat.TCPAbortOnClose = &value
|
||||
case "TCPDeferAcceptDrop":
|
||||
procNetstat.TcpExt.TCPDeferAcceptDrop = &value
|
||||
procNetstat.TCPDeferAcceptDrop = &value
|
||||
case "IPReversePathFilter":
|
||||
procNetstat.TcpExt.IPReversePathFilter = &value
|
||||
procNetstat.IPReversePathFilter = &value
|
||||
case "TCPTimeWaitOverflow":
|
||||
procNetstat.TcpExt.TCPTimeWaitOverflow = &value
|
||||
procNetstat.TCPTimeWaitOverflow = &value
|
||||
case "TCPReqQFullDoCookies":
|
||||
procNetstat.TcpExt.TCPReqQFullDoCookies = &value
|
||||
procNetstat.TCPReqQFullDoCookies = &value
|
||||
case "TCPReqQFullDrop":
|
||||
procNetstat.TcpExt.TCPReqQFullDrop = &value
|
||||
procNetstat.TCPReqQFullDrop = &value
|
||||
case "TCPRetransFail":
|
||||
procNetstat.TcpExt.TCPRetransFail = &value
|
||||
procNetstat.TCPRetransFail = &value
|
||||
case "TCPRcvCoalesce":
|
||||
procNetstat.TcpExt.TCPRcvCoalesce = &value
|
||||
procNetstat.TCPRcvCoalesce = &value
|
||||
case "TCPRcvQDrop":
|
||||
procNetstat.TcpExt.TCPRcvQDrop = &value
|
||||
procNetstat.TCPRcvQDrop = &value
|
||||
case "TCPOFOQueue":
|
||||
procNetstat.TcpExt.TCPOFOQueue = &value
|
||||
procNetstat.TCPOFOQueue = &value
|
||||
case "TCPOFODrop":
|
||||
procNetstat.TcpExt.TCPOFODrop = &value
|
||||
procNetstat.TCPOFODrop = &value
|
||||
case "TCPOFOMerge":
|
||||
procNetstat.TcpExt.TCPOFOMerge = &value
|
||||
procNetstat.TCPOFOMerge = &value
|
||||
case "TCPChallengeACK":
|
||||
procNetstat.TcpExt.TCPChallengeACK = &value
|
||||
procNetstat.TCPChallengeACK = &value
|
||||
case "TCPSYNChallenge":
|
||||
procNetstat.TcpExt.TCPSYNChallenge = &value
|
||||
procNetstat.TCPSYNChallenge = &value
|
||||
case "TCPFastOpenActive":
|
||||
procNetstat.TcpExt.TCPFastOpenActive = &value
|
||||
procNetstat.TCPFastOpenActive = &value
|
||||
case "TCPFastOpenActiveFail":
|
||||
procNetstat.TcpExt.TCPFastOpenActiveFail = &value
|
||||
procNetstat.TCPFastOpenActiveFail = &value
|
||||
case "TCPFastOpenPassive":
|
||||
procNetstat.TcpExt.TCPFastOpenPassive = &value
|
||||
procNetstat.TCPFastOpenPassive = &value
|
||||
case "TCPFastOpenPassiveFail":
|
||||
procNetstat.TcpExt.TCPFastOpenPassiveFail = &value
|
||||
procNetstat.TCPFastOpenPassiveFail = &value
|
||||
case "TCPFastOpenListenOverflow":
|
||||
procNetstat.TcpExt.TCPFastOpenListenOverflow = &value
|
||||
procNetstat.TCPFastOpenListenOverflow = &value
|
||||
case "TCPFastOpenCookieReqd":
|
||||
procNetstat.TcpExt.TCPFastOpenCookieReqd = &value
|
||||
procNetstat.TCPFastOpenCookieReqd = &value
|
||||
case "TCPFastOpenBlackhole":
|
||||
procNetstat.TcpExt.TCPFastOpenBlackhole = &value
|
||||
procNetstat.TCPFastOpenBlackhole = &value
|
||||
case "TCPSpuriousRtxHostQueues":
|
||||
procNetstat.TcpExt.TCPSpuriousRtxHostQueues = &value
|
||||
procNetstat.TCPSpuriousRtxHostQueues = &value
|
||||
case "BusyPollRxPackets":
|
||||
procNetstat.TcpExt.BusyPollRxPackets = &value
|
||||
procNetstat.BusyPollRxPackets = &value
|
||||
case "TCPAutoCorking":
|
||||
procNetstat.TcpExt.TCPAutoCorking = &value
|
||||
procNetstat.TCPAutoCorking = &value
|
||||
case "TCPFromZeroWindowAdv":
|
||||
procNetstat.TcpExt.TCPFromZeroWindowAdv = &value
|
||||
procNetstat.TCPFromZeroWindowAdv = &value
|
||||
case "TCPToZeroWindowAdv":
|
||||
procNetstat.TcpExt.TCPToZeroWindowAdv = &value
|
||||
procNetstat.TCPToZeroWindowAdv = &value
|
||||
case "TCPWantZeroWindowAdv":
|
||||
procNetstat.TcpExt.TCPWantZeroWindowAdv = &value
|
||||
procNetstat.TCPWantZeroWindowAdv = &value
|
||||
case "TCPSynRetrans":
|
||||
procNetstat.TcpExt.TCPSynRetrans = &value
|
||||
procNetstat.TCPSynRetrans = &value
|
||||
case "TCPOrigDataSent":
|
||||
procNetstat.TcpExt.TCPOrigDataSent = &value
|
||||
procNetstat.TCPOrigDataSent = &value
|
||||
case "TCPHystartTrainDetect":
|
||||
procNetstat.TcpExt.TCPHystartTrainDetect = &value
|
||||
procNetstat.TCPHystartTrainDetect = &value
|
||||
case "TCPHystartTrainCwnd":
|
||||
procNetstat.TcpExt.TCPHystartTrainCwnd = &value
|
||||
procNetstat.TCPHystartTrainCwnd = &value
|
||||
case "TCPHystartDelayDetect":
|
||||
procNetstat.TcpExt.TCPHystartDelayDetect = &value
|
||||
procNetstat.TCPHystartDelayDetect = &value
|
||||
case "TCPHystartDelayCwnd":
|
||||
procNetstat.TcpExt.TCPHystartDelayCwnd = &value
|
||||
procNetstat.TCPHystartDelayCwnd = &value
|
||||
case "TCPACKSkippedSynRecv":
|
||||
procNetstat.TcpExt.TCPACKSkippedSynRecv = &value
|
||||
procNetstat.TCPACKSkippedSynRecv = &value
|
||||
case "TCPACKSkippedPAWS":
|
||||
procNetstat.TcpExt.TCPACKSkippedPAWS = &value
|
||||
procNetstat.TCPACKSkippedPAWS = &value
|
||||
case "TCPACKSkippedSeq":
|
||||
procNetstat.TcpExt.TCPACKSkippedSeq = &value
|
||||
procNetstat.TCPACKSkippedSeq = &value
|
||||
case "TCPACKSkippedFinWait2":
|
||||
procNetstat.TcpExt.TCPACKSkippedFinWait2 = &value
|
||||
procNetstat.TCPACKSkippedFinWait2 = &value
|
||||
case "TCPACKSkippedTimeWait":
|
||||
procNetstat.TcpExt.TCPACKSkippedTimeWait = &value
|
||||
procNetstat.TCPACKSkippedTimeWait = &value
|
||||
case "TCPACKSkippedChallenge":
|
||||
procNetstat.TcpExt.TCPACKSkippedChallenge = &value
|
||||
procNetstat.TCPACKSkippedChallenge = &value
|
||||
case "TCPWinProbe":
|
||||
procNetstat.TcpExt.TCPWinProbe = &value
|
||||
procNetstat.TCPWinProbe = &value
|
||||
case "TCPKeepAlive":
|
||||
procNetstat.TcpExt.TCPKeepAlive = &value
|
||||
procNetstat.TCPKeepAlive = &value
|
||||
case "TCPMTUPFail":
|
||||
procNetstat.TcpExt.TCPMTUPFail = &value
|
||||
procNetstat.TCPMTUPFail = &value
|
||||
case "TCPMTUPSuccess":
|
||||
procNetstat.TcpExt.TCPMTUPSuccess = &value
|
||||
procNetstat.TCPMTUPSuccess = &value
|
||||
case "TCPWqueueTooBig":
|
||||
procNetstat.TcpExt.TCPWqueueTooBig = &value
|
||||
procNetstat.TCPWqueueTooBig = &value
|
||||
}
|
||||
case "IpExt":
|
||||
switch key {
|
||||
case "InNoRoutes":
|
||||
procNetstat.IpExt.InNoRoutes = &value
|
||||
procNetstat.InNoRoutes = &value
|
||||
case "InTruncatedPkts":
|
||||
procNetstat.IpExt.InTruncatedPkts = &value
|
||||
procNetstat.InTruncatedPkts = &value
|
||||
case "InMcastPkts":
|
||||
procNetstat.IpExt.InMcastPkts = &value
|
||||
procNetstat.InMcastPkts = &value
|
||||
case "OutMcastPkts":
|
||||
procNetstat.IpExt.OutMcastPkts = &value
|
||||
procNetstat.OutMcastPkts = &value
|
||||
case "InBcastPkts":
|
||||
procNetstat.IpExt.InBcastPkts = &value
|
||||
procNetstat.InBcastPkts = &value
|
||||
case "OutBcastPkts":
|
||||
procNetstat.IpExt.OutBcastPkts = &value
|
||||
procNetstat.OutBcastPkts = &value
|
||||
case "InOctets":
|
||||
procNetstat.IpExt.InOctets = &value
|
||||
procNetstat.InOctets = &value
|
||||
case "OutOctets":
|
||||
procNetstat.IpExt.OutOctets = &value
|
||||
procNetstat.OutOctets = &value
|
||||
case "InMcastOctets":
|
||||
procNetstat.IpExt.InMcastOctets = &value
|
||||
procNetstat.InMcastOctets = &value
|
||||
case "OutMcastOctets":
|
||||
procNetstat.IpExt.OutMcastOctets = &value
|
||||
procNetstat.OutMcastOctets = &value
|
||||
case "InBcastOctets":
|
||||
procNetstat.IpExt.InBcastOctets = &value
|
||||
procNetstat.InBcastOctets = &value
|
||||
case "OutBcastOctets":
|
||||
procNetstat.IpExt.OutBcastOctets = &value
|
||||
procNetstat.OutBcastOctets = &value
|
||||
case "InCsumErrors":
|
||||
procNetstat.IpExt.InCsumErrors = &value
|
||||
procNetstat.InCsumErrors = &value
|
||||
case "InNoECTPkts":
|
||||
procNetstat.IpExt.InNoECTPkts = &value
|
||||
procNetstat.InNoECTPkts = &value
|
||||
case "InECT1Pkts":
|
||||
procNetstat.IpExt.InECT1Pkts = &value
|
||||
procNetstat.InECT1Pkts = &value
|
||||
case "InECT0Pkts":
|
||||
procNetstat.IpExt.InECT0Pkts = &value
|
||||
procNetstat.InECT0Pkts = &value
|
||||
case "InCEPkts":
|
||||
procNetstat.IpExt.InCEPkts = &value
|
||||
procNetstat.InCEPkts = &value
|
||||
case "ReasmOverlaps":
|
||||
procNetstat.IpExt.ReasmOverlaps = &value
|
||||
procNetstat.ReasmOverlaps = &value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
vendor/github.com/prometheus/procfs/proc_smaps.go
generated
vendored
4
vendor/github.com/prometheus/procfs/proc_smaps.go
generated
vendored
|
|
@ -19,7 +19,6 @@ package procfs
|
|||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
|
@ -29,7 +28,7 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
// match the header line before each mapped zone in `/proc/pid/smaps`.
|
||||
// Match the header line before each mapped zone in `/proc/pid/smaps`.
|
||||
procSMapsHeaderLine = regexp.MustCompile(`^[a-f0-9].*$`)
|
||||
)
|
||||
|
||||
|
|
@ -117,7 +116,6 @@ func (p Proc) procSMapsRollupManual() (ProcSMapsRollup, error) {
|
|||
func (s *ProcSMapsRollup) parseLine(line string) error {
|
||||
kv := strings.SplitN(line, ":", 2)
|
||||
if len(kv) != 2 {
|
||||
fmt.Println(line)
|
||||
return errors.New("invalid net/dev line, missing colon")
|
||||
}
|
||||
|
||||
|
|
|
|||
120
vendor/github.com/prometheus/procfs/proc_snmp.go
generated
vendored
120
vendor/github.com/prometheus/procfs/proc_snmp.go
generated
vendored
|
|
@ -173,138 +173,138 @@ func parseSnmp(r io.Reader, fileName string) (ProcSnmp, error) {
|
|||
case "Ip":
|
||||
switch key {
|
||||
case "Forwarding":
|
||||
procSnmp.Ip.Forwarding = &value
|
||||
procSnmp.Forwarding = &value
|
||||
case "DefaultTTL":
|
||||
procSnmp.Ip.DefaultTTL = &value
|
||||
procSnmp.DefaultTTL = &value
|
||||
case "InReceives":
|
||||
procSnmp.Ip.InReceives = &value
|
||||
procSnmp.InReceives = &value
|
||||
case "InHdrErrors":
|
||||
procSnmp.Ip.InHdrErrors = &value
|
||||
procSnmp.InHdrErrors = &value
|
||||
case "InAddrErrors":
|
||||
procSnmp.Ip.InAddrErrors = &value
|
||||
procSnmp.InAddrErrors = &value
|
||||
case "ForwDatagrams":
|
||||
procSnmp.Ip.ForwDatagrams = &value
|
||||
procSnmp.ForwDatagrams = &value
|
||||
case "InUnknownProtos":
|
||||
procSnmp.Ip.InUnknownProtos = &value
|
||||
procSnmp.InUnknownProtos = &value
|
||||
case "InDiscards":
|
||||
procSnmp.Ip.InDiscards = &value
|
||||
procSnmp.InDiscards = &value
|
||||
case "InDelivers":
|
||||
procSnmp.Ip.InDelivers = &value
|
||||
procSnmp.InDelivers = &value
|
||||
case "OutRequests":
|
||||
procSnmp.Ip.OutRequests = &value
|
||||
procSnmp.OutRequests = &value
|
||||
case "OutDiscards":
|
||||
procSnmp.Ip.OutDiscards = &value
|
||||
procSnmp.OutDiscards = &value
|
||||
case "OutNoRoutes":
|
||||
procSnmp.Ip.OutNoRoutes = &value
|
||||
procSnmp.OutNoRoutes = &value
|
||||
case "ReasmTimeout":
|
||||
procSnmp.Ip.ReasmTimeout = &value
|
||||
procSnmp.ReasmTimeout = &value
|
||||
case "ReasmReqds":
|
||||
procSnmp.Ip.ReasmReqds = &value
|
||||
procSnmp.ReasmReqds = &value
|
||||
case "ReasmOKs":
|
||||
procSnmp.Ip.ReasmOKs = &value
|
||||
procSnmp.ReasmOKs = &value
|
||||
case "ReasmFails":
|
||||
procSnmp.Ip.ReasmFails = &value
|
||||
procSnmp.ReasmFails = &value
|
||||
case "FragOKs":
|
||||
procSnmp.Ip.FragOKs = &value
|
||||
procSnmp.FragOKs = &value
|
||||
case "FragFails":
|
||||
procSnmp.Ip.FragFails = &value
|
||||
procSnmp.FragFails = &value
|
||||
case "FragCreates":
|
||||
procSnmp.Ip.FragCreates = &value
|
||||
procSnmp.FragCreates = &value
|
||||
}
|
||||
case "Icmp":
|
||||
switch key {
|
||||
case "InMsgs":
|
||||
procSnmp.Icmp.InMsgs = &value
|
||||
procSnmp.InMsgs = &value
|
||||
case "InErrors":
|
||||
procSnmp.Icmp.InErrors = &value
|
||||
case "InCsumErrors":
|
||||
procSnmp.Icmp.InCsumErrors = &value
|
||||
case "InDestUnreachs":
|
||||
procSnmp.Icmp.InDestUnreachs = &value
|
||||
procSnmp.InDestUnreachs = &value
|
||||
case "InTimeExcds":
|
||||
procSnmp.Icmp.InTimeExcds = &value
|
||||
procSnmp.InTimeExcds = &value
|
||||
case "InParmProbs":
|
||||
procSnmp.Icmp.InParmProbs = &value
|
||||
procSnmp.InParmProbs = &value
|
||||
case "InSrcQuenchs":
|
||||
procSnmp.Icmp.InSrcQuenchs = &value
|
||||
procSnmp.InSrcQuenchs = &value
|
||||
case "InRedirects":
|
||||
procSnmp.Icmp.InRedirects = &value
|
||||
procSnmp.InRedirects = &value
|
||||
case "InEchos":
|
||||
procSnmp.Icmp.InEchos = &value
|
||||
procSnmp.InEchos = &value
|
||||
case "InEchoReps":
|
||||
procSnmp.Icmp.InEchoReps = &value
|
||||
procSnmp.InEchoReps = &value
|
||||
case "InTimestamps":
|
||||
procSnmp.Icmp.InTimestamps = &value
|
||||
procSnmp.InTimestamps = &value
|
||||
case "InTimestampReps":
|
||||
procSnmp.Icmp.InTimestampReps = &value
|
||||
procSnmp.InTimestampReps = &value
|
||||
case "InAddrMasks":
|
||||
procSnmp.Icmp.InAddrMasks = &value
|
||||
procSnmp.InAddrMasks = &value
|
||||
case "InAddrMaskReps":
|
||||
procSnmp.Icmp.InAddrMaskReps = &value
|
||||
procSnmp.InAddrMaskReps = &value
|
||||
case "OutMsgs":
|
||||
procSnmp.Icmp.OutMsgs = &value
|
||||
procSnmp.OutMsgs = &value
|
||||
case "OutErrors":
|
||||
procSnmp.Icmp.OutErrors = &value
|
||||
procSnmp.OutErrors = &value
|
||||
case "OutDestUnreachs":
|
||||
procSnmp.Icmp.OutDestUnreachs = &value
|
||||
procSnmp.OutDestUnreachs = &value
|
||||
case "OutTimeExcds":
|
||||
procSnmp.Icmp.OutTimeExcds = &value
|
||||
procSnmp.OutTimeExcds = &value
|
||||
case "OutParmProbs":
|
||||
procSnmp.Icmp.OutParmProbs = &value
|
||||
procSnmp.OutParmProbs = &value
|
||||
case "OutSrcQuenchs":
|
||||
procSnmp.Icmp.OutSrcQuenchs = &value
|
||||
procSnmp.OutSrcQuenchs = &value
|
||||
case "OutRedirects":
|
||||
procSnmp.Icmp.OutRedirects = &value
|
||||
procSnmp.OutRedirects = &value
|
||||
case "OutEchos":
|
||||
procSnmp.Icmp.OutEchos = &value
|
||||
procSnmp.OutEchos = &value
|
||||
case "OutEchoReps":
|
||||
procSnmp.Icmp.OutEchoReps = &value
|
||||
procSnmp.OutEchoReps = &value
|
||||
case "OutTimestamps":
|
||||
procSnmp.Icmp.OutTimestamps = &value
|
||||
procSnmp.OutTimestamps = &value
|
||||
case "OutTimestampReps":
|
||||
procSnmp.Icmp.OutTimestampReps = &value
|
||||
procSnmp.OutTimestampReps = &value
|
||||
case "OutAddrMasks":
|
||||
procSnmp.Icmp.OutAddrMasks = &value
|
||||
procSnmp.OutAddrMasks = &value
|
||||
case "OutAddrMaskReps":
|
||||
procSnmp.Icmp.OutAddrMaskReps = &value
|
||||
procSnmp.OutAddrMaskReps = &value
|
||||
}
|
||||
case "IcmpMsg":
|
||||
switch key {
|
||||
case "InType3":
|
||||
procSnmp.IcmpMsg.InType3 = &value
|
||||
procSnmp.InType3 = &value
|
||||
case "OutType3":
|
||||
procSnmp.IcmpMsg.OutType3 = &value
|
||||
procSnmp.OutType3 = &value
|
||||
}
|
||||
case "Tcp":
|
||||
switch key {
|
||||
case "RtoAlgorithm":
|
||||
procSnmp.Tcp.RtoAlgorithm = &value
|
||||
procSnmp.RtoAlgorithm = &value
|
||||
case "RtoMin":
|
||||
procSnmp.Tcp.RtoMin = &value
|
||||
procSnmp.RtoMin = &value
|
||||
case "RtoMax":
|
||||
procSnmp.Tcp.RtoMax = &value
|
||||
procSnmp.RtoMax = &value
|
||||
case "MaxConn":
|
||||
procSnmp.Tcp.MaxConn = &value
|
||||
procSnmp.MaxConn = &value
|
||||
case "ActiveOpens":
|
||||
procSnmp.Tcp.ActiveOpens = &value
|
||||
procSnmp.ActiveOpens = &value
|
||||
case "PassiveOpens":
|
||||
procSnmp.Tcp.PassiveOpens = &value
|
||||
procSnmp.PassiveOpens = &value
|
||||
case "AttemptFails":
|
||||
procSnmp.Tcp.AttemptFails = &value
|
||||
procSnmp.AttemptFails = &value
|
||||
case "EstabResets":
|
||||
procSnmp.Tcp.EstabResets = &value
|
||||
procSnmp.EstabResets = &value
|
||||
case "CurrEstab":
|
||||
procSnmp.Tcp.CurrEstab = &value
|
||||
procSnmp.CurrEstab = &value
|
||||
case "InSegs":
|
||||
procSnmp.Tcp.InSegs = &value
|
||||
procSnmp.InSegs = &value
|
||||
case "OutSegs":
|
||||
procSnmp.Tcp.OutSegs = &value
|
||||
procSnmp.OutSegs = &value
|
||||
case "RetransSegs":
|
||||
procSnmp.Tcp.RetransSegs = &value
|
||||
procSnmp.RetransSegs = &value
|
||||
case "InErrs":
|
||||
procSnmp.Tcp.InErrs = &value
|
||||
procSnmp.InErrs = &value
|
||||
case "OutRsts":
|
||||
procSnmp.Tcp.OutRsts = &value
|
||||
procSnmp.OutRsts = &value
|
||||
case "InCsumErrors":
|
||||
procSnmp.Tcp.InCsumErrors = &value
|
||||
}
|
||||
|
|
|
|||
150
vendor/github.com/prometheus/procfs/proc_snmp6.go
generated
vendored
150
vendor/github.com/prometheus/procfs/proc_snmp6.go
generated
vendored
|
|
@ -182,161 +182,161 @@ func parseSNMP6Stats(r io.Reader) (ProcSnmp6, error) {
|
|||
case "Ip6":
|
||||
switch key {
|
||||
case "InReceives":
|
||||
procSnmp6.Ip6.InReceives = &value
|
||||
procSnmp6.InReceives = &value
|
||||
case "InHdrErrors":
|
||||
procSnmp6.Ip6.InHdrErrors = &value
|
||||
procSnmp6.InHdrErrors = &value
|
||||
case "InTooBigErrors":
|
||||
procSnmp6.Ip6.InTooBigErrors = &value
|
||||
procSnmp6.InTooBigErrors = &value
|
||||
case "InNoRoutes":
|
||||
procSnmp6.Ip6.InNoRoutes = &value
|
||||
procSnmp6.InNoRoutes = &value
|
||||
case "InAddrErrors":
|
||||
procSnmp6.Ip6.InAddrErrors = &value
|
||||
procSnmp6.InAddrErrors = &value
|
||||
case "InUnknownProtos":
|
||||
procSnmp6.Ip6.InUnknownProtos = &value
|
||||
procSnmp6.InUnknownProtos = &value
|
||||
case "InTruncatedPkts":
|
||||
procSnmp6.Ip6.InTruncatedPkts = &value
|
||||
procSnmp6.InTruncatedPkts = &value
|
||||
case "InDiscards":
|
||||
procSnmp6.Ip6.InDiscards = &value
|
||||
procSnmp6.InDiscards = &value
|
||||
case "InDelivers":
|
||||
procSnmp6.Ip6.InDelivers = &value
|
||||
procSnmp6.InDelivers = &value
|
||||
case "OutForwDatagrams":
|
||||
procSnmp6.Ip6.OutForwDatagrams = &value
|
||||
procSnmp6.OutForwDatagrams = &value
|
||||
case "OutRequests":
|
||||
procSnmp6.Ip6.OutRequests = &value
|
||||
procSnmp6.OutRequests = &value
|
||||
case "OutDiscards":
|
||||
procSnmp6.Ip6.OutDiscards = &value
|
||||
procSnmp6.OutDiscards = &value
|
||||
case "OutNoRoutes":
|
||||
procSnmp6.Ip6.OutNoRoutes = &value
|
||||
procSnmp6.OutNoRoutes = &value
|
||||
case "ReasmTimeout":
|
||||
procSnmp6.Ip6.ReasmTimeout = &value
|
||||
procSnmp6.ReasmTimeout = &value
|
||||
case "ReasmReqds":
|
||||
procSnmp6.Ip6.ReasmReqds = &value
|
||||
procSnmp6.ReasmReqds = &value
|
||||
case "ReasmOKs":
|
||||
procSnmp6.Ip6.ReasmOKs = &value
|
||||
procSnmp6.ReasmOKs = &value
|
||||
case "ReasmFails":
|
||||
procSnmp6.Ip6.ReasmFails = &value
|
||||
procSnmp6.ReasmFails = &value
|
||||
case "FragOKs":
|
||||
procSnmp6.Ip6.FragOKs = &value
|
||||
procSnmp6.FragOKs = &value
|
||||
case "FragFails":
|
||||
procSnmp6.Ip6.FragFails = &value
|
||||
procSnmp6.FragFails = &value
|
||||
case "FragCreates":
|
||||
procSnmp6.Ip6.FragCreates = &value
|
||||
procSnmp6.FragCreates = &value
|
||||
case "InMcastPkts":
|
||||
procSnmp6.Ip6.InMcastPkts = &value
|
||||
procSnmp6.InMcastPkts = &value
|
||||
case "OutMcastPkts":
|
||||
procSnmp6.Ip6.OutMcastPkts = &value
|
||||
procSnmp6.OutMcastPkts = &value
|
||||
case "InOctets":
|
||||
procSnmp6.Ip6.InOctets = &value
|
||||
procSnmp6.InOctets = &value
|
||||
case "OutOctets":
|
||||
procSnmp6.Ip6.OutOctets = &value
|
||||
procSnmp6.OutOctets = &value
|
||||
case "InMcastOctets":
|
||||
procSnmp6.Ip6.InMcastOctets = &value
|
||||
procSnmp6.InMcastOctets = &value
|
||||
case "OutMcastOctets":
|
||||
procSnmp6.Ip6.OutMcastOctets = &value
|
||||
procSnmp6.OutMcastOctets = &value
|
||||
case "InBcastOctets":
|
||||
procSnmp6.Ip6.InBcastOctets = &value
|
||||
procSnmp6.InBcastOctets = &value
|
||||
case "OutBcastOctets":
|
||||
procSnmp6.Ip6.OutBcastOctets = &value
|
||||
procSnmp6.OutBcastOctets = &value
|
||||
case "InNoECTPkts":
|
||||
procSnmp6.Ip6.InNoECTPkts = &value
|
||||
procSnmp6.InNoECTPkts = &value
|
||||
case "InECT1Pkts":
|
||||
procSnmp6.Ip6.InECT1Pkts = &value
|
||||
procSnmp6.InECT1Pkts = &value
|
||||
case "InECT0Pkts":
|
||||
procSnmp6.Ip6.InECT0Pkts = &value
|
||||
procSnmp6.InECT0Pkts = &value
|
||||
case "InCEPkts":
|
||||
procSnmp6.Ip6.InCEPkts = &value
|
||||
procSnmp6.InCEPkts = &value
|
||||
|
||||
}
|
||||
case "Icmp6":
|
||||
switch key {
|
||||
case "InMsgs":
|
||||
procSnmp6.Icmp6.InMsgs = &value
|
||||
procSnmp6.InMsgs = &value
|
||||
case "InErrors":
|
||||
procSnmp6.Icmp6.InErrors = &value
|
||||
case "OutMsgs":
|
||||
procSnmp6.Icmp6.OutMsgs = &value
|
||||
procSnmp6.OutMsgs = &value
|
||||
case "OutErrors":
|
||||
procSnmp6.Icmp6.OutErrors = &value
|
||||
procSnmp6.OutErrors = &value
|
||||
case "InCsumErrors":
|
||||
procSnmp6.Icmp6.InCsumErrors = &value
|
||||
case "InDestUnreachs":
|
||||
procSnmp6.Icmp6.InDestUnreachs = &value
|
||||
procSnmp6.InDestUnreachs = &value
|
||||
case "InPktTooBigs":
|
||||
procSnmp6.Icmp6.InPktTooBigs = &value
|
||||
procSnmp6.InPktTooBigs = &value
|
||||
case "InTimeExcds":
|
||||
procSnmp6.Icmp6.InTimeExcds = &value
|
||||
procSnmp6.InTimeExcds = &value
|
||||
case "InParmProblems":
|
||||
procSnmp6.Icmp6.InParmProblems = &value
|
||||
procSnmp6.InParmProblems = &value
|
||||
case "InEchos":
|
||||
procSnmp6.Icmp6.InEchos = &value
|
||||
procSnmp6.InEchos = &value
|
||||
case "InEchoReplies":
|
||||
procSnmp6.Icmp6.InEchoReplies = &value
|
||||
procSnmp6.InEchoReplies = &value
|
||||
case "InGroupMembQueries":
|
||||
procSnmp6.Icmp6.InGroupMembQueries = &value
|
||||
procSnmp6.InGroupMembQueries = &value
|
||||
case "InGroupMembResponses":
|
||||
procSnmp6.Icmp6.InGroupMembResponses = &value
|
||||
procSnmp6.InGroupMembResponses = &value
|
||||
case "InGroupMembReductions":
|
||||
procSnmp6.Icmp6.InGroupMembReductions = &value
|
||||
procSnmp6.InGroupMembReductions = &value
|
||||
case "InRouterSolicits":
|
||||
procSnmp6.Icmp6.InRouterSolicits = &value
|
||||
procSnmp6.InRouterSolicits = &value
|
||||
case "InRouterAdvertisements":
|
||||
procSnmp6.Icmp6.InRouterAdvertisements = &value
|
||||
procSnmp6.InRouterAdvertisements = &value
|
||||
case "InNeighborSolicits":
|
||||
procSnmp6.Icmp6.InNeighborSolicits = &value
|
||||
procSnmp6.InNeighborSolicits = &value
|
||||
case "InNeighborAdvertisements":
|
||||
procSnmp6.Icmp6.InNeighborAdvertisements = &value
|
||||
procSnmp6.InNeighborAdvertisements = &value
|
||||
case "InRedirects":
|
||||
procSnmp6.Icmp6.InRedirects = &value
|
||||
procSnmp6.InRedirects = &value
|
||||
case "InMLDv2Reports":
|
||||
procSnmp6.Icmp6.InMLDv2Reports = &value
|
||||
procSnmp6.InMLDv2Reports = &value
|
||||
case "OutDestUnreachs":
|
||||
procSnmp6.Icmp6.OutDestUnreachs = &value
|
||||
procSnmp6.OutDestUnreachs = &value
|
||||
case "OutPktTooBigs":
|
||||
procSnmp6.Icmp6.OutPktTooBigs = &value
|
||||
procSnmp6.OutPktTooBigs = &value
|
||||
case "OutTimeExcds":
|
||||
procSnmp6.Icmp6.OutTimeExcds = &value
|
||||
procSnmp6.OutTimeExcds = &value
|
||||
case "OutParmProblems":
|
||||
procSnmp6.Icmp6.OutParmProblems = &value
|
||||
procSnmp6.OutParmProblems = &value
|
||||
case "OutEchos":
|
||||
procSnmp6.Icmp6.OutEchos = &value
|
||||
procSnmp6.OutEchos = &value
|
||||
case "OutEchoReplies":
|
||||
procSnmp6.Icmp6.OutEchoReplies = &value
|
||||
procSnmp6.OutEchoReplies = &value
|
||||
case "OutGroupMembQueries":
|
||||
procSnmp6.Icmp6.OutGroupMembQueries = &value
|
||||
procSnmp6.OutGroupMembQueries = &value
|
||||
case "OutGroupMembResponses":
|
||||
procSnmp6.Icmp6.OutGroupMembResponses = &value
|
||||
procSnmp6.OutGroupMembResponses = &value
|
||||
case "OutGroupMembReductions":
|
||||
procSnmp6.Icmp6.OutGroupMembReductions = &value
|
||||
procSnmp6.OutGroupMembReductions = &value
|
||||
case "OutRouterSolicits":
|
||||
procSnmp6.Icmp6.OutRouterSolicits = &value
|
||||
procSnmp6.OutRouterSolicits = &value
|
||||
case "OutRouterAdvertisements":
|
||||
procSnmp6.Icmp6.OutRouterAdvertisements = &value
|
||||
procSnmp6.OutRouterAdvertisements = &value
|
||||
case "OutNeighborSolicits":
|
||||
procSnmp6.Icmp6.OutNeighborSolicits = &value
|
||||
procSnmp6.OutNeighborSolicits = &value
|
||||
case "OutNeighborAdvertisements":
|
||||
procSnmp6.Icmp6.OutNeighborAdvertisements = &value
|
||||
procSnmp6.OutNeighborAdvertisements = &value
|
||||
case "OutRedirects":
|
||||
procSnmp6.Icmp6.OutRedirects = &value
|
||||
procSnmp6.OutRedirects = &value
|
||||
case "OutMLDv2Reports":
|
||||
procSnmp6.Icmp6.OutMLDv2Reports = &value
|
||||
procSnmp6.OutMLDv2Reports = &value
|
||||
case "InType1":
|
||||
procSnmp6.Icmp6.InType1 = &value
|
||||
procSnmp6.InType1 = &value
|
||||
case "InType134":
|
||||
procSnmp6.Icmp6.InType134 = &value
|
||||
procSnmp6.InType134 = &value
|
||||
case "InType135":
|
||||
procSnmp6.Icmp6.InType135 = &value
|
||||
procSnmp6.InType135 = &value
|
||||
case "InType136":
|
||||
procSnmp6.Icmp6.InType136 = &value
|
||||
procSnmp6.InType136 = &value
|
||||
case "InType143":
|
||||
procSnmp6.Icmp6.InType143 = &value
|
||||
procSnmp6.InType143 = &value
|
||||
case "OutType133":
|
||||
procSnmp6.Icmp6.OutType133 = &value
|
||||
procSnmp6.OutType133 = &value
|
||||
case "OutType135":
|
||||
procSnmp6.Icmp6.OutType135 = &value
|
||||
procSnmp6.OutType135 = &value
|
||||
case "OutType136":
|
||||
procSnmp6.Icmp6.OutType136 = &value
|
||||
procSnmp6.OutType136 = &value
|
||||
case "OutType143":
|
||||
procSnmp6.Icmp6.OutType143 = &value
|
||||
procSnmp6.OutType143 = &value
|
||||
}
|
||||
case "Udp6":
|
||||
switch key {
|
||||
|
|
@ -355,7 +355,7 @@ func parseSNMP6Stats(r io.Reader) (ProcSnmp6, error) {
|
|||
case "InCsumErrors":
|
||||
procSnmp6.Udp6.InCsumErrors = &value
|
||||
case "IgnoredMulti":
|
||||
procSnmp6.Udp6.IgnoredMulti = &value
|
||||
procSnmp6.IgnoredMulti = &value
|
||||
}
|
||||
case "UdpLite6":
|
||||
switch key {
|
||||
|
|
|
|||
18
vendor/github.com/prometheus/procfs/proc_status.go
generated
vendored
18
vendor/github.com/prometheus/procfs/proc_status.go
generated
vendored
|
|
@ -146,7 +146,11 @@ func (s *ProcStatus) fillStatus(k string, vString string, vUint uint64, vUintByt
|
|||
}
|
||||
}
|
||||
case "NSpid":
|
||||
s.NSpids = calcNSPidsList(vString)
|
||||
nspids, err := calcNSPidsList(vString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.NSpids = nspids
|
||||
case "VmPeak":
|
||||
s.VmPeak = vUintBytes
|
||||
case "VmSize":
|
||||
|
|
@ -222,17 +226,17 @@ func calcCpusAllowedList(cpuString string) []uint64 {
|
|||
return g
|
||||
}
|
||||
|
||||
func calcNSPidsList(nspidsString string) []uint64 {
|
||||
s := strings.Split(nspidsString, " ")
|
||||
func calcNSPidsList(nspidsString string) ([]uint64, error) {
|
||||
s := strings.Split(nspidsString, "\t")
|
||||
var nspids []uint64
|
||||
|
||||
for _, nspid := range s {
|
||||
nspid, _ := strconv.ParseUint(nspid, 10, 64)
|
||||
if nspid == 0 {
|
||||
continue
|
||||
nspid, err := strconv.ParseUint(nspid, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nspids = append(nspids, nspid)
|
||||
}
|
||||
|
||||
return nspids
|
||||
return nspids, nil
|
||||
}
|
||||
|
|
|
|||
2
vendor/github.com/prometheus/procfs/proc_sys.go
generated
vendored
2
vendor/github.com/prometheus/procfs/proc_sys.go
generated
vendored
|
|
@ -21,7 +21,7 @@ import (
|
|||
)
|
||||
|
||||
func sysctlToPath(sysctl string) string {
|
||||
return strings.Replace(sysctl, ".", "/", -1)
|
||||
return strings.ReplaceAll(sysctl, ".", "/")
|
||||
}
|
||||
|
||||
func (fs FS) SysctlStrings(sysctl string) ([]string, error) {
|
||||
|
|
|
|||
22
vendor/github.com/prometheus/procfs/softirqs.go
generated
vendored
22
vendor/github.com/prometheus/procfs/softirqs.go
generated
vendored
|
|
@ -68,8 +68,8 @@ func parseSoftirqs(r io.Reader) (Softirqs, error) {
|
|||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
switch {
|
||||
case parts[0] == "HI:":
|
||||
switch parts[0] {
|
||||
case "HI:":
|
||||
perCPU := parts[1:]
|
||||
softirqs.Hi = make([]uint64, len(perCPU))
|
||||
for i, count := range perCPU {
|
||||
|
|
@ -77,7 +77,7 @@ func parseSoftirqs(r io.Reader) (Softirqs, error) {
|
|||
return Softirqs{}, fmt.Errorf("%w: couldn't parse %q (HI%d): %w", ErrFileParse, count, i, err)
|
||||
}
|
||||
}
|
||||
case parts[0] == "TIMER:":
|
||||
case "TIMER:":
|
||||
perCPU := parts[1:]
|
||||
softirqs.Timer = make([]uint64, len(perCPU))
|
||||
for i, count := range perCPU {
|
||||
|
|
@ -85,7 +85,7 @@ func parseSoftirqs(r io.Reader) (Softirqs, error) {
|
|||
return Softirqs{}, fmt.Errorf("%w: couldn't parse %q (TIMER%d): %w", ErrFileParse, count, i, err)
|
||||
}
|
||||
}
|
||||
case parts[0] == "NET_TX:":
|
||||
case "NET_TX:":
|
||||
perCPU := parts[1:]
|
||||
softirqs.NetTx = make([]uint64, len(perCPU))
|
||||
for i, count := range perCPU {
|
||||
|
|
@ -93,7 +93,7 @@ func parseSoftirqs(r io.Reader) (Softirqs, error) {
|
|||
return Softirqs{}, fmt.Errorf("%w: couldn't parse %q (NET_TX%d): %w", ErrFileParse, count, i, err)
|
||||
}
|
||||
}
|
||||
case parts[0] == "NET_RX:":
|
||||
case "NET_RX:":
|
||||
perCPU := parts[1:]
|
||||
softirqs.NetRx = make([]uint64, len(perCPU))
|
||||
for i, count := range perCPU {
|
||||
|
|
@ -101,7 +101,7 @@ func parseSoftirqs(r io.Reader) (Softirqs, error) {
|
|||
return Softirqs{}, fmt.Errorf("%w: couldn't parse %q (NET_RX%d): %w", ErrFileParse, count, i, err)
|
||||
}
|
||||
}
|
||||
case parts[0] == "BLOCK:":
|
||||
case "BLOCK:":
|
||||
perCPU := parts[1:]
|
||||
softirqs.Block = make([]uint64, len(perCPU))
|
||||
for i, count := range perCPU {
|
||||
|
|
@ -109,7 +109,7 @@ func parseSoftirqs(r io.Reader) (Softirqs, error) {
|
|||
return Softirqs{}, fmt.Errorf("%w: couldn't parse %q (BLOCK%d): %w", ErrFileParse, count, i, err)
|
||||
}
|
||||
}
|
||||
case parts[0] == "IRQ_POLL:":
|
||||
case "IRQ_POLL:":
|
||||
perCPU := parts[1:]
|
||||
softirqs.IRQPoll = make([]uint64, len(perCPU))
|
||||
for i, count := range perCPU {
|
||||
|
|
@ -117,7 +117,7 @@ func parseSoftirqs(r io.Reader) (Softirqs, error) {
|
|||
return Softirqs{}, fmt.Errorf("%w: couldn't parse %q (IRQ_POLL%d): %w", ErrFileParse, count, i, err)
|
||||
}
|
||||
}
|
||||
case parts[0] == "TASKLET:":
|
||||
case "TASKLET:":
|
||||
perCPU := parts[1:]
|
||||
softirqs.Tasklet = make([]uint64, len(perCPU))
|
||||
for i, count := range perCPU {
|
||||
|
|
@ -125,7 +125,7 @@ func parseSoftirqs(r io.Reader) (Softirqs, error) {
|
|||
return Softirqs{}, fmt.Errorf("%w: couldn't parse %q (TASKLET%d): %w", ErrFileParse, count, i, err)
|
||||
}
|
||||
}
|
||||
case parts[0] == "SCHED:":
|
||||
case "SCHED:":
|
||||
perCPU := parts[1:]
|
||||
softirqs.Sched = make([]uint64, len(perCPU))
|
||||
for i, count := range perCPU {
|
||||
|
|
@ -133,7 +133,7 @@ func parseSoftirqs(r io.Reader) (Softirqs, error) {
|
|||
return Softirqs{}, fmt.Errorf("%w: couldn't parse %q (SCHED%d): %w", ErrFileParse, count, i, err)
|
||||
}
|
||||
}
|
||||
case parts[0] == "HRTIMER:":
|
||||
case "HRTIMER:":
|
||||
perCPU := parts[1:]
|
||||
softirqs.HRTimer = make([]uint64, len(perCPU))
|
||||
for i, count := range perCPU {
|
||||
|
|
@ -141,7 +141,7 @@ func parseSoftirqs(r io.Reader) (Softirqs, error) {
|
|||
return Softirqs{}, fmt.Errorf("%w: couldn't parse %q (HRTIMER%d): %w", ErrFileParse, count, i, err)
|
||||
}
|
||||
}
|
||||
case parts[0] == "RCU:":
|
||||
case "RCU:":
|
||||
perCPU := parts[1:]
|
||||
softirqs.RCU = make([]uint64, len(perCPU))
|
||||
for i, count := range perCPU {
|
||||
|
|
|
|||
2
vendor/go.opentelemetry.io/contrib/bridges/prometheus/BENCHMARKS.md
generated
vendored
2
vendor/go.opentelemetry.io/contrib/bridges/prometheus/BENCHMARKS.md
generated
vendored
|
|
@ -1,4 +1,4 @@
|
|||
## Summary
|
||||
# Prometheus Benchmarks
|
||||
|
||||
Using the Prometheus bridge and the OTLP exporter adds roughly ~50% to the CPU and memory overhead of an application compared to serving a Prometheus HTTP endpoint for metrics.
|
||||
|
||||
|
|
|
|||
9
vendor/go.opentelemetry.io/contrib/instrumentation/runtime/version.go
generated
vendored
9
vendor/go.opentelemetry.io/contrib/instrumentation/runtime/version.go
generated
vendored
|
|
@ -5,13 +5,6 @@ package runtime // import "go.opentelemetry.io/contrib/instrumentation/runtime"
|
|||
|
||||
// Version is the current release version of the runtime instrumentation.
|
||||
func Version() string {
|
||||
return "0.60.0"
|
||||
return "0.61.0"
|
||||
// This string is updated by the pre_release.sh script during release
|
||||
}
|
||||
|
||||
// SemVersion is the semantic version to be supplied to tracer/meter creation.
|
||||
//
|
||||
// Deprecated: Use [Version] instead.
|
||||
func SemVersion() string {
|
||||
return Version()
|
||||
}
|
||||
|
|
|
|||
33
vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/config.go
generated
vendored
33
vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/config.go
generated
vendored
|
|
@ -13,6 +13,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
|
|
@ -359,8 +360,9 @@ func WithTimeout(duration time.Duration) Option {
|
|||
// explicitly returns a backoff time in the response, that time will take
|
||||
// precedence over these settings.
|
||||
//
|
||||
// These settings do not define any network retry strategy. That is entirely
|
||||
// handled by the gRPC ClientConn.
|
||||
// These settings define the retry strategy implemented by the exporter.
|
||||
// These settings do not define any network retry strategy.
|
||||
// That is handled by the gRPC ClientConn.
|
||||
//
|
||||
// If unset, the default retry policy will be used. It will retry the export
|
||||
// 5 seconds after receiving a retryable error and increase exponentially
|
||||
|
|
@ -442,13 +444,15 @@ func convHeaders(s string) (map[string]string, error) {
|
|||
continue
|
||||
}
|
||||
|
||||
escKey, e := url.PathUnescape(rawKey)
|
||||
if e != nil {
|
||||
key := strings.TrimSpace(rawKey)
|
||||
|
||||
// Validate the key.
|
||||
if !isValidHeaderKey(key) {
|
||||
err = errors.Join(err, fmt.Errorf("invalid header key: %s", rawKey))
|
||||
continue
|
||||
}
|
||||
key := strings.TrimSpace(escKey)
|
||||
|
||||
// Only decode the value.
|
||||
escVal, e := url.PathUnescape(rawVal)
|
||||
if e != nil {
|
||||
err = errors.Join(err, fmt.Errorf("invalid header value: %s", rawVal))
|
||||
|
|
@ -651,3 +655,22 @@ func fallback[T any](val T) resolver[T] {
|
|||
return s
|
||||
}
|
||||
}
|
||||
|
||||
func isValidHeaderKey(key string) bool {
|
||||
if key == "" {
|
||||
return false
|
||||
}
|
||||
for _, c := range key {
|
||||
if !isTokenChar(c) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isTokenChar(c rune) bool {
|
||||
return c <= unicode.MaxASCII && (unicode.IsLetter(c) ||
|
||||
unicode.IsDigit(c) ||
|
||||
c == '!' || c == '#' || c == '$' || c == '%' || c == '&' || c == '\'' || c == '*' ||
|
||||
c == '+' || c == '-' || c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Code created by gotmpl. DO NOT MODIFY.
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/otlp/retry/retry.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
|
|
@ -14,7 +14,7 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/cenkalti/backoff/v5"
|
||||
)
|
||||
|
||||
// DefaultConfig are the recommended defaults to use.
|
||||
|
|
@ -77,12 +77,12 @@ func (c Config) RequestFunc(evaluate EvaluateFunc) RequestFunc {
|
|||
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
||||
Multiplier: backoff.DefaultMultiplier,
|
||||
MaxInterval: c.MaxInterval,
|
||||
MaxElapsedTime: c.MaxElapsedTime,
|
||||
Stop: backoff.Stop,
|
||||
Clock: backoff.SystemClock,
|
||||
}
|
||||
b.Reset()
|
||||
|
||||
maxElapsedTime := c.MaxElapsedTime
|
||||
startTime := time.Now()
|
||||
|
||||
for {
|
||||
err := fn(ctx)
|
||||
if err == nil {
|
||||
|
|
@ -94,21 +94,17 @@ func (c Config) RequestFunc(evaluate EvaluateFunc) RequestFunc {
|
|||
return err
|
||||
}
|
||||
|
||||
bOff := b.NextBackOff()
|
||||
if bOff == backoff.Stop {
|
||||
if maxElapsedTime != 0 && time.Since(startTime) > maxElapsedTime {
|
||||
return fmt.Errorf("max retry time elapsed: %w", err)
|
||||
}
|
||||
|
||||
// Wait for the greater of the backoff or throttle delay.
|
||||
var delay time.Duration
|
||||
if bOff > throttle {
|
||||
delay = bOff
|
||||
} else {
|
||||
elapsed := b.GetElapsedTime()
|
||||
if b.MaxElapsedTime != 0 && elapsed+throttle > b.MaxElapsedTime {
|
||||
return fmt.Errorf("max retry time would elapse: %w", err)
|
||||
}
|
||||
delay = throttle
|
||||
bOff := b.NextBackOff()
|
||||
delay := max(throttle, bOff)
|
||||
|
||||
elapsed := time.Since(startTime)
|
||||
if maxElapsedTime != 0 && elapsed+throttle > maxElapsedTime {
|
||||
return fmt.Errorf("max retry time would elapse: %w", err)
|
||||
}
|
||||
|
||||
if ctxErr := waitFunc(ctx, delay); ctxErr != nil {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Code created by gotmpl. DO NOT MODIFY.
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/otlp/otlplog/transform/log.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
|
|
@ -257,7 +257,7 @@ func stringSliceValues(vals []string) []*cpb.AnyValue {
|
|||
return converted
|
||||
}
|
||||
|
||||
// Attrs transforms a slice of [api.KeyValue] into OTLP key-values.
|
||||
// LogAttrs transforms a slice of [api.KeyValue] into OTLP key-values.
|
||||
func LogAttrs(attrs []api.KeyValue) []*cpb.KeyValue {
|
||||
if len(attrs) == 0 {
|
||||
return nil
|
||||
|
|
|
|||
2
vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/version.go
generated
vendored
2
vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/version.go
generated
vendored
|
|
@ -5,5 +5,5 @@ package otlploggrpc // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/o
|
|||
|
||||
// Version is the current release version of the OpenTelemetry OTLP over gRPC logs exporter in use.
|
||||
func Version() string {
|
||||
return "0.11.0"
|
||||
return "0.12.2"
|
||||
}
|
||||
|
|
|
|||
29
vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp/client.go
generated
vendored
29
vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp/client.go
generated
vendored
|
|
@ -44,20 +44,23 @@ func newNoopClient() *client {
|
|||
|
||||
// newHTTPClient creates a new HTTP log client.
|
||||
func newHTTPClient(cfg config) (*client, error) {
|
||||
hc := &http.Client{
|
||||
Transport: ourTransport,
|
||||
Timeout: cfg.timeout.Value,
|
||||
}
|
||||
|
||||
if cfg.tlsCfg.Value != nil || cfg.proxy.Value != nil {
|
||||
clonedTransport := ourTransport.Clone()
|
||||
hc.Transport = clonedTransport
|
||||
|
||||
if cfg.tlsCfg.Value != nil {
|
||||
clonedTransport.TLSClientConfig = cfg.tlsCfg.Value
|
||||
hc := cfg.httpClient
|
||||
if hc == nil {
|
||||
hc = &http.Client{
|
||||
Transport: ourTransport,
|
||||
Timeout: cfg.timeout.Value,
|
||||
}
|
||||
if cfg.proxy.Value != nil {
|
||||
clonedTransport.Proxy = cfg.proxy.Value
|
||||
|
||||
if cfg.tlsCfg.Value != nil || cfg.proxy.Value != nil {
|
||||
clonedTransport := ourTransport.Clone()
|
||||
hc.Transport = clonedTransport
|
||||
|
||||
if cfg.tlsCfg.Value != nil {
|
||||
clonedTransport.TLSClientConfig = cfg.tlsCfg.Value
|
||||
}
|
||||
if cfg.proxy.Value != nil {
|
||||
clonedTransport.Proxy = cfg.proxy.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
48
vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp/config.go
generated
vendored
48
vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp/config.go
generated
vendored
|
|
@ -14,6 +14,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp/internal/retry"
|
||||
|
|
@ -94,6 +95,7 @@ type config struct {
|
|||
timeout setting[time.Duration]
|
||||
proxy setting[HTTPTransportProxyFunc]
|
||||
retryCfg setting[retry.Config]
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
func newConfig(options []Option) config {
|
||||
|
|
@ -343,6 +345,25 @@ func WithProxy(pf HTTPTransportProxyFunc) Option {
|
|||
})
|
||||
}
|
||||
|
||||
// WithHTTPClient sets the HTTP client to used by the exporter.
|
||||
//
|
||||
// This option will take precedence over [WithProxy], [WithTimeout],
|
||||
// [WithTLSClientConfig] options as well as OTEL_EXPORTER_OTLP_CERTIFICATE,
|
||||
// OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE, OTEL_EXPORTER_OTLP_TIMEOUT,
|
||||
// OTEL_EXPORTER_OTLP_LOGS_TIMEOUT environment variables.
|
||||
//
|
||||
// Timeout and all other fields of the passed [http.Client] are left intact.
|
||||
//
|
||||
// Be aware that passing an HTTP client with transport like
|
||||
// [go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp.NewTransport] can
|
||||
// cause the client to be instrumented twice and cause infinite recursion.
|
||||
func WithHTTPClient(c *http.Client) Option {
|
||||
return fnOpt(func(cfg config) config {
|
||||
cfg.httpClient = c
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
// setting is a configuration setting value.
|
||||
type setting[T any] struct {
|
||||
Value T
|
||||
|
|
@ -544,13 +565,15 @@ func convHeaders(s string) (map[string]string, error) {
|
|||
continue
|
||||
}
|
||||
|
||||
escKey, e := url.PathUnescape(rawKey)
|
||||
if e != nil {
|
||||
key := strings.TrimSpace(rawKey)
|
||||
|
||||
// Validate the key.
|
||||
if !isValidHeaderKey(key) {
|
||||
err = errors.Join(err, fmt.Errorf("invalid header key: %s", rawKey))
|
||||
continue
|
||||
}
|
||||
key := strings.TrimSpace(escKey)
|
||||
|
||||
// Only decode the value.
|
||||
escVal, e := url.PathUnescape(rawVal)
|
||||
if e != nil {
|
||||
err = errors.Join(err, fmt.Errorf("invalid header value: %s", rawVal))
|
||||
|
|
@ -600,3 +623,22 @@ func fallback[T any](val T) resolver[T] {
|
|||
return s
|
||||
}
|
||||
}
|
||||
|
||||
func isValidHeaderKey(key string) bool {
|
||||
if key == "" {
|
||||
return false
|
||||
}
|
||||
for _, c := range key {
|
||||
if !isTokenChar(c) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isTokenChar(c rune) bool {
|
||||
return c <= unicode.MaxASCII && (unicode.IsLetter(c) ||
|
||||
unicode.IsDigit(c) ||
|
||||
c == '!' || c == '#' || c == '$' || c == '%' || c == '&' || c == '\'' || c == '*' ||
|
||||
c == '+' || c == '-' || c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Code created by gotmpl. DO NOT MODIFY.
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/otlp/retry/retry.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
|
|
@ -14,7 +14,7 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/cenkalti/backoff/v5"
|
||||
)
|
||||
|
||||
// DefaultConfig are the recommended defaults to use.
|
||||
|
|
@ -77,12 +77,12 @@ func (c Config) RequestFunc(evaluate EvaluateFunc) RequestFunc {
|
|||
RandomizationFactor: backoff.DefaultRandomizationFactor,
|
||||
Multiplier: backoff.DefaultMultiplier,
|
||||
MaxInterval: c.MaxInterval,
|
||||
MaxElapsedTime: c.MaxElapsedTime,
|
||||
Stop: backoff.Stop,
|
||||
Clock: backoff.SystemClock,
|
||||
}
|
||||
b.Reset()
|
||||
|
||||
maxElapsedTime := c.MaxElapsedTime
|
||||
startTime := time.Now()
|
||||
|
||||
for {
|
||||
err := fn(ctx)
|
||||
if err == nil {
|
||||
|
|
@ -94,21 +94,17 @@ func (c Config) RequestFunc(evaluate EvaluateFunc) RequestFunc {
|
|||
return err
|
||||
}
|
||||
|
||||
bOff := b.NextBackOff()
|
||||
if bOff == backoff.Stop {
|
||||
if maxElapsedTime != 0 && time.Since(startTime) > maxElapsedTime {
|
||||
return fmt.Errorf("max retry time elapsed: %w", err)
|
||||
}
|
||||
|
||||
// Wait for the greater of the backoff or throttle delay.
|
||||
var delay time.Duration
|
||||
if bOff > throttle {
|
||||
delay = bOff
|
||||
} else {
|
||||
elapsed := b.GetElapsedTime()
|
||||
if b.MaxElapsedTime != 0 && elapsed+throttle > b.MaxElapsedTime {
|
||||
return fmt.Errorf("max retry time would elapse: %w", err)
|
||||
}
|
||||
delay = throttle
|
||||
bOff := b.NextBackOff()
|
||||
delay := max(throttle, bOff)
|
||||
|
||||
elapsed := time.Since(startTime)
|
||||
if maxElapsedTime != 0 && elapsed+throttle > maxElapsedTime {
|
||||
return fmt.Errorf("max retry time would elapse: %w", err)
|
||||
}
|
||||
|
||||
if ctxErr := waitFunc(ctx, delay); ctxErr != nil {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Code created by gotmpl. DO NOT MODIFY.
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/otlp/otlplog/transform/log.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
|
|
@ -257,7 +257,7 @@ func stringSliceValues(vals []string) []*cpb.AnyValue {
|
|||
return converted
|
||||
}
|
||||
|
||||
// Attrs transforms a slice of [api.KeyValue] into OTLP key-values.
|
||||
// LogAttrs transforms a slice of [api.KeyValue] into OTLP key-values.
|
||||
func LogAttrs(attrs []api.KeyValue) []*cpb.KeyValue {
|
||||
if len(attrs) == 0 {
|
||||
return nil
|
||||
|
|
|
|||
2
vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp/version.go
generated
vendored
2
vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp/version.go
generated
vendored
|
|
@ -5,5 +5,5 @@ package otlploghttp // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/o
|
|||
|
||||
// Version is the current release version of the OpenTelemetry OTLP over HTTP/protobuf logs exporter in use.
|
||||
func Version() string {
|
||||
return "0.11.0"
|
||||
return "0.12.2"
|
||||
}
|
||||
|
|
|
|||
5
vendor/go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/config.go
generated
vendored
5
vendor/go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/config.go
generated
vendored
|
|
@ -238,8 +238,9 @@ func WithTimeout(duration time.Duration) Option {
|
|||
// explicitly returns a backoff time in the response, that time will take
|
||||
// precedence over these settings.
|
||||
//
|
||||
// These settings do not define any network retry strategy. That is entirely
|
||||
// handled by the gRPC ClientConn.
|
||||
// These settings define the retry strategy implemented by the exporter.
|
||||
// These settings do not define any network retry strategy.
|
||||
// That is handled by the gRPC ClientConn.
|
||||
//
|
||||
// If unset, the default retry policy will be used. It will retry the export
|
||||
// 5 seconds after receiving a retryable error and increase exponentially
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
// Code created by gotmpl. DO NOT MODIFY.
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/otlp/envconfig/envconfig.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package envconfig provides functionality to parse configuration from
|
||||
// environment variables.
|
||||
package envconfig // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/envconfig"
|
||||
|
||||
import (
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue