Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: otel tracing #899

Merged
merged 85 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
aa10d2d
init jaeger
JordanSussman Jun 28, 2023
4301fbe
wip: otel gin and gorm
plyr4 Jun 28, 2023
ced4353
wip: added context to ONLY CreateSchedule
plyr4 Jun 28, 2023
fe6b3eb
update docker compose
JordanSussman Jun 28, 2023
7752be5
attempt to mask gorm values
JordanSussman Jun 28, 2023
817dee1
Merge branch 'feat/otel-tracing' of github.com:go-vela/server into fe…
jbrockopp Jun 28, 2023
0799b91
chore: save work
jbrockopp Jun 28, 2023
453d5bc
fix github creds
JordanSussman Jun 28, 2023
52ce323
add back vault
JordanSussman Jun 28, 2023
208410b
make the serviceName as vela-server
JordanSussman Jun 28, 2023
efd0487
pass around the proper context
JordanSussman Jun 29, 2023
646f64b
cli flag and opt-in enable tracing
plyr4 Jun 29, 2023
e78ff24
Merge branch 'feat/otel-tracing' of github.com:go-vela/server into fe…
plyr4 Jun 29, 2023
6f86666
refactor: move code into tracing package
plyr4 Jun 29, 2023
2b7db12
tweak: order of vars and flags
plyr4 Jun 29, 2023
fedcee4
Merge branch 'main' into feat/otel-tracing
JordanSussman Jun 29, 2023
570d148
fix depends on
JordanSussman Jun 29, 2023
120a5e7
make linter happy
JordanSussman Jun 29, 2023
541025d
fix tests
JordanSussman Jun 29, 2023
4fe0b68
chore: merge with main
plyr4 Aug 16, 2023
b695599
enhance: add context to scm
plyr4 Sep 18, 2023
4d42413
chore: replace TODO with ctx
plyr4 Sep 18, 2023
a043735
chore: merge with main
plyr4 Sep 19, 2023
36a7b60
chore: merge with main
plyr4 Sep 19, 2023
304456f
Merge branch 'main' of github.com:go-vela/server into feat/otel-tracing
plyr4 Sep 19, 2023
d43a49d
chore: merge with main
plyr4 Sep 19, 2023
793f17d
chore: merge with main
plyr4 Aug 1, 2024
f597009
chore: rm launch.json
plyr4 Aug 1, 2024
d9da5a3
chore: headers and misc cleanup
plyr4 Aug 1, 2024
7b4e110
chore: golangci
plyr4 Aug 1, 2024
602c183
Merge branch 'main' of github.com:go-vela/server into feat/otel-tracing
plyr4 Aug 13, 2024
da924d5
enhance: supply parent context to all db calls
plyr4 Aug 13, 2024
ae40a65
fix: lint
plyr4 Aug 13, 2024
72636ba
Merge branch 'main' into enhance/db-ctx
plyr4 Aug 15, 2024
eba4507
Merge branch 'main' of github.com:go-vela/server into feat/otel-tracing
plyr4 Aug 19, 2024
e99b4ce
Merge branch 'main' into enhance/db-ctx
plyr4 Aug 19, 2024
15c8115
chore: merge with main
plyr4 Aug 20, 2024
19703bf
Merge branch 'enhance/db-ctx' of github.com:go-vela/server into feat/…
plyr4 Aug 20, 2024
d921b3e
chore: merge with enhance/db-ctx
plyr4 Aug 20, 2024
e590f23
enhance: simple head samplers for ratio and rate limit
plyr4 Aug 21, 2024
c22be46
Merge branch 'main' of github.com:go-vela/server into feat/otel-tracing
plyr4 Aug 21, 2024
3aa5b8d
enhance: customizable config, code cleanup, middleware
plyr4 Aug 21, 2024
868fa97
fix: lint
plyr4 Aug 21, 2024
02899ae
enhance: customizable resource and span attributes for restrictive co…
plyr4 Aug 26, 2024
14619d2
fix: min tls version
plyr4 Aug 26, 2024
3d273ce
trigger pipeline
plyr4 Aug 26, 2024
0397f5a
fix: compose options
plyr4 Aug 26, 2024
fcc8b23
fix: add disabled tracing config to db test
plyr4 Aug 26, 2024
bac7ab8
Merge branch 'main' into feat/otel-tracing
plyr4 Aug 26, 2024
fba0da2
Merge branch 'main' into feat/otel-tracing
plyr4 Aug 26, 2024
33bf135
fix: remove unused var
plyr4 Aug 26, 2024
50e82aa
Merge branch 'feat/otel-tracing' of github.com:go-vela/server into fe…
plyr4 Aug 26, 2024
093b912
Merge branch 'main' into feat/otel-tracing
plyr4 Sep 4, 2024
069c4d7
chore: go mod tidy
plyr4 Sep 4, 2024
85b67b1
chore: revert compose var
plyr4 Sep 4, 2024
37a6e9b
Merge branch 'main' into feat/otel-tracing
plyr4 Sep 5, 2024
0aa5de5
chore: remove bogus file
plyr4 Sep 5, 2024
938fe6b
Merge branch 'feat/otel-tracing' of github.com:go-vela/server into fe…
plyr4 Sep 5, 2024
e945cd8
Merge branch 'main' into feat/otel-tracing
KellyMerrick Sep 6, 2024
3cb9bb3
Merge branch 'main' into feat/otel-tracing
plyr4 Sep 9, 2024
ee8a03a
feat: tracing middleware tests
plyr4 Sep 9, 2024
cd973d5
chore: lint
plyr4 Sep 9, 2024
c95193b
Merge branch 'main' into feat/otel-tracing
plyr4 Sep 11, 2024
7b27836
enhance: configurable min-tls
plyr4 Sep 11, 2024
0525a14
fix: vela-server default service name
plyr4 Sep 11, 2024
00e22c4
chore: remove default env resource attrs
plyr4 Sep 11, 2024
9e8c026
Merge branch 'main' into feat/otel-tracing
ecrupper Sep 11, 2024
7e5ac7e
enhance: refactored attribute mapping
plyr4 Sep 11, 2024
ebc4427
Merge branch 'feat/otel-tracing' of github.com:go-vela/server into fe…
plyr4 Sep 11, 2024
2339384
chore: cleanup
plyr4 Sep 11, 2024
8379079
chore: lint
plyr4 Sep 11, 2024
e5f3a29
chore: cleanup
plyr4 Sep 11, 2024
f03fa06
chore: cleanup
plyr4 Sep 11, 2024
18ea2c4
chore: cleanup
plyr4 Sep 11, 2024
bb6b59a
fix: better url parsing
plyr4 Sep 11, 2024
2c4fe9e
enhance: docs links and flag clarification
plyr4 Sep 11, 2024
6db9549
enhance: more flag clarification
plyr4 Sep 11, 2024
c604d5f
Merge branch 'main' into feat/otel-tracing
wass3rw3rk Sep 12, 2024
d12f845
fix: conditionally apply scm tracing
plyr4 Sep 12, 2024
eb0d870
chore: tests
plyr4 Sep 12, 2024
7c98676
fix: do not cancel goroutine context used after http response closes
plyr4 Sep 12, 2024
f4a949d
fix: do not cancel goroutine context used after http response closes
plyr4 Sep 12, 2024
2139c9c
fix: avoid double responses
plyr4 Sep 12, 2024
da2bbee
chore: lint...
plyr4 Sep 12, 2024
1e20dd9
fix: scm test client
plyr4 Sep 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion api/build/approve.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package build

import (
"context"
"fmt"
"net/http"
"strings"
Expand Down Expand Up @@ -110,7 +111,7 @@ func ApproveBuild(c *gin.Context) {

// publish the build to the queue
go Enqueue(
ctx,
context.WithoutCancel(ctx),
queue.FromGinContext(c),
database.FromContext(c),
models.ToItem(b),
Expand Down
3 changes: 2 additions & 1 deletion api/build/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package build

import (
"context"
"fmt"
"net/http"

Expand Down Expand Up @@ -149,7 +150,7 @@ func CreateBuild(c *gin.Context) {

// publish the build to the queue
go Enqueue(
ctx,
context.WithoutCancel(ctx),
queue.FromGinContext(c),
database.FromContext(c),
item,
Expand Down
3 changes: 2 additions & 1 deletion api/build/restart.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package build

import (
"context"
"fmt"
"net/http"
"strings"
Expand Down Expand Up @@ -157,7 +158,7 @@ func RestartBuild(c *gin.Context) {

// publish the build to the queue
go Enqueue(
ctx,
context.WithoutCancel(ctx),
queue.FromGinContext(c),
database.FromContext(c),
item,
Expand Down
21 changes: 18 additions & 3 deletions api/webhook/post.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@

defer func() {
// send API call to update the webhook
_, err = database.FromContext(c).UpdateHook(ctx, h)

Check failure on line 193 in api/webhook/post.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] api/webhook/post.go#L193

Non-inherited new context, use function like `context.WithXXX` instead (contextcheck)
Raw output
api/webhook/post.go:193:32: Non-inherited new context, use function like `context.WithXXX` instead (contextcheck)
		_, err = database.FromContext(c).UpdateHook(ctx, h)
		                             ^
if err != nil {
l.Errorf("unable to update webhook %s/%d: %v", r.GetFullName(), h.GetNumber(), err)
}
Expand Down Expand Up @@ -434,7 +434,7 @@
deployment := webhook.Deployment

deployment.SetRepoID(repo.GetID())
deployment.SetBuilds([]*library.Build{b.ToLibrary()})

Check failure on line 437 in api/webhook/post.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] api/webhook/post.go#L437

SA1019: library.Build is deprecated: use Build from github.com/go-vela/server/api/types instead. (staticcheck)
Raw output
api/webhook/post.go:437:29: SA1019: library.Build is deprecated: use Build from github.com/go-vela/server/api/types instead. (staticcheck)
				deployment.SetBuilds([]*library.Build{b.ToLibrary()})
				                        ^

dr, err := database.FromContext(c).CreateDeployment(c, deployment)
if err != nil {
Expand Down Expand Up @@ -489,8 +489,6 @@
}
}

c.JSON(http.StatusCreated, b)

// regardless of whether the build is published to queue, we want to attempt to auto-cancel if no errors
defer func() {
if err == nil && build.ShouldAutoCancel(p.Metadata.AutoCancel, b, repo.GetBranch()) {
Expand Down Expand Up @@ -531,6 +529,10 @@
}
}()

// track if we have already responded to the http request
// helps prevent multiple responses to the same request in the event of errors
responded := false

// if the webhook was from a Pull event from a forked repository, verify it is allowed to run
if webhook.PullRequest.IsFromFork {
l.Tracef("inside %s workflow for fork PR build %s/%d", repo.GetApproveBuild(), repo.GetFullName(), b.GetNumber())
Expand All @@ -540,6 +542,8 @@
err = gatekeepBuild(c, b, repo)
if err != nil {
util.HandleError(c, http.StatusInternalServerError, err)
} else {
c.JSON(http.StatusCreated, b)
}

return
Expand All @@ -550,6 +554,8 @@
err = gatekeepBuild(c, b, repo)
if err != nil {
util.HandleError(c, http.StatusInternalServerError, err)
} else {
c.JSON(http.StatusCreated, b)
}

return
Expand All @@ -564,12 +570,16 @@
contributor, err := scm.FromContext(c).RepoContributor(ctx, repo.GetOwner(), b.GetSender(), repo.GetOrg(), repo.GetName())
if err != nil {
util.HandleError(c, http.StatusInternalServerError, err)

responded = true
}

if !contributor {
err = gatekeepBuild(c, b, repo)
if err != nil {
util.HandleError(c, http.StatusInternalServerError, err)
} else if !responded {
c.JSON(http.StatusCreated, b)
}

return
Expand All @@ -591,12 +601,17 @@

// publish the build to the queue
go build.Enqueue(
ctx,
context.WithoutCancel(ctx),
queue.FromGinContext(c),
database.FromContext(c),
item,
b.GetHost(),
)

// respond only when necessary
if !responded {
c.JSON(http.StatusCreated, b)
}
}

// handleRepositoryEvent is a helper function that processes repository events from the SCM and updates
Expand Down Expand Up @@ -644,7 +659,7 @@
case "archived", "unarchived", constants.ActionEdited:
l.Debugf("repository action %s for %s", h.GetEventAction(), r.GetFullName())
// send call to get repository from database
dbRepo, err := database.FromContext(c).GetRepoForOrg(ctx, r.GetOrg(), r.GetName())

Check failure on line 662 in api/webhook/post.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] api/webhook/post.go#L662

Non-inherited new context, use function like `context.WithXXX` instead (contextcheck)
Raw output
api/webhook/post.go:662:38: Non-inherited new context, use function like `context.WithXXX` instead (contextcheck)
		dbRepo, err := database.FromContext(c).GetRepoForOrg(ctx, r.GetOrg(), r.GetName())
		                                   ^
if err != nil {
retErr := fmt.Errorf("%s: failed to get repo %s: %w", baseErr, r.GetFullName(), err)

Expand All @@ -655,7 +670,7 @@
}

// send API call to capture the last hook for the repo
lastHook, err := database.FromContext(c).LastHookForRepo(ctx, dbRepo)

Check failure on line 673 in api/webhook/post.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] api/webhook/post.go#L673

Non-inherited new context, use function like `context.WithXXX` instead (contextcheck)
Raw output
api/webhook/post.go:673:40: Non-inherited new context, use function like `context.WithXXX` instead (contextcheck)
		lastHook, err := database.FromContext(c).LastHookForRepo(ctx, dbRepo)
		                                     ^
if err != nil {
retErr := fmt.Errorf("unable to get last hook for repo %s: %w", r.GetFullName(), err)

Expand Down
4 changes: 4 additions & 0 deletions cmd/vela-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/go-vela/server/queue"
"github.com/go-vela/server/scm"
"github.com/go-vela/server/secret"
"github.com/go-vela/server/tracing"
"github.com/go-vela/server/version"
"github.com/go-vela/types/constants"
)
Expand Down Expand Up @@ -279,6 +280,9 @@ func main() {
// Add Source Flags
app.Flags = append(app.Flags, scm.Flags...)

// Add Tracing Flags
app.Flags = append(app.Flags, tracing.Flags...)

if err = app.Run(os.Args); err != nil {
logrus.Fatal(err)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/vela-server/schedule.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ func processSchedule(ctx context.Context, s *api.Schedule, settings *settings.Pl

// publish the build to the queue
go build.Enqueue(
ctx,
context.WithoutCancel(ctx),
queue,
database,
item,
Expand Down
4 changes: 3 additions & 1 deletion cmd/vela-server/scm.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import (
"github.com/urfave/cli/v2"

"github.com/go-vela/server/scm"
"github.com/go-vela/server/tracing"
)

// helper function to setup the scm from the CLI arguments.
func setupSCM(c *cli.Context) (scm.Service, error) {
func setupSCM(c *cli.Context, tc *tracing.Client) (scm.Service, error) {
logrus.Debug("creating scm client from CLI configuration")

// scm configuration
Expand All @@ -24,6 +25,7 @@ func setupSCM(c *cli.Context) (scm.Service, error) {
StatusContext: c.String("scm.context"),
WebUIAddress: c.String("webui-addr"),
Scopes: c.StringSlice("scm.scopes"),
Tracing: tc,
}

// setup the scm
Expand Down
21 changes: 19 additions & 2 deletions cmd/vela-server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/go-vela/server/queue"
"github.com/go-vela/server/router"
"github.com/go-vela/server/router/middleware"
"github.com/go-vela/server/tracing"
)

//nolint:funlen,gocyclo // ignore function length and cyclomatic complexity
Expand Down Expand Up @@ -78,7 +79,21 @@ func server(c *cli.Context) error {
return err
}

database, err := database.FromCLIContext(c)
tc, err := tracing.FromCLIContext(c)
if err != nil {
return err
}

if tc.EnableTracing {
defer func() {
err := tc.TracerProvider.Shutdown(context.Background())
if err != nil {
logrus.Errorf("unable to shutdown tracer provider: %v", err)
}
}()
}

database, err := database.FromCLIContext(c, tc)
if err != nil {
return err
}
Expand All @@ -93,7 +108,7 @@ func server(c *cli.Context) error {
return err
}

scm, err := setupSCM(c)
scm, err := setupSCM(c, tc)
if err != nil {
return err
}
Expand Down Expand Up @@ -189,6 +204,8 @@ func server(c *cli.Context) error {
middleware.DefaultRepoEventsMask(c.Int64("default-repo-events-mask")),
middleware.DefaultRepoApproveBuild(c.String("default-repo-approve-build")),
middleware.ScheduleFrequency(c.Duration("schedule-minimum-frequency")),
middleware.TracingClient(tc),
middleware.TracingInstrumentation(tc),
)

addr, err := url.Parse(c.String("server-addr"))
Expand Down
5 changes: 4 additions & 1 deletion database/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (

"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"

"github.com/go-vela/server/tracing"
)

const key = "database"
Expand Down Expand Up @@ -38,7 +40,7 @@ func ToContext(c Setter, d Interface) {
}

// FromCLIContext creates and returns a database engine from the urfave/cli context.
func FromCLIContext(c *cli.Context) (Interface, error) {
func FromCLIContext(c *cli.Context, tc *tracing.Client) (Interface, error) {
logrus.Debug("creating database engine from CLI configuration")

return New(
Expand All @@ -54,5 +56,6 @@ func FromCLIContext(c *cli.Context) (Interface, error) {
WithLogSlowThreshold(c.Duration("database.log.slow_threshold")),
WithLogShowSQL(c.Bool("database.log.show_sql")),
WithSkipCreation(c.Bool("database.skip_creation")),
WithTracing(tc),
)
}
4 changes: 3 additions & 1 deletion database/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (

"github.com/gin-gonic/gin"
"github.com/urfave/cli/v2"

"github.com/go-vela/server/tracing"
)

func TestDatabase_FromContext(t *testing.T) {
Expand Down Expand Up @@ -132,7 +134,7 @@ func TestDatabase_FromCLIContext(t *testing.T) {
// run tests
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
_, err := FromCLIContext(test.context)
_, err := FromCLIContext(test.context, &tracing.Client{Config: tracing.Config{EnableTracing: false}})

if test.failure {
if err == nil {
Expand Down
20 changes: 19 additions & 1 deletion database/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"time"

"github.com/sirupsen/logrus"
"github.com/uptrace/opentelemetry-go-extra/otelgorm"
"gorm.io/driver/postgres"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
Expand All @@ -28,6 +29,7 @@ import (
"github.com/go-vela/server/database/step"
"github.com/go-vela/server/database/user"
"github.com/go-vela/server/database/worker"
"github.com/go-vela/server/tracing"
"github.com/go-vela/types/constants"
)

Expand Down Expand Up @@ -70,6 +72,8 @@ type (
ctx context.Context
// sirupsen/logrus logger used in database functions
logger *logrus.Entry
// configurations related to telemetry/tracing
tracing *tracing.Client

settings.SettingsInterface
build.BuildInterface
Expand Down Expand Up @@ -105,7 +109,7 @@ func New(opts ...EngineOpt) (Interface, error) {
e.client = new(gorm.DB)
e.config = new(config)
e.logger = new(logrus.Entry)
e.ctx = context.TODO()
e.ctx = context.Background()

// apply all provided configuration options
for _, opt := range opts {
Expand Down Expand Up @@ -191,6 +195,19 @@ func New(opts ...EngineOpt) (Interface, error) {
return nil, err
}

// initialize otel tracing if enabled
if e.tracing.EnableTracing {
otelPlugin := otelgorm.NewPlugin(
otelgorm.WithTracerProvider(e.tracing.TracerProvider),
otelgorm.WithoutQueryVariables(),
)

err := e.client.Use(otelPlugin)
if err != nil {
return nil, err
}
}

// set the maximum amount of time a connection may be reused
db.SetConnMaxLifetime(e.config.ConnectionLife)
// set the maximum number of connections in the idle connection pool
Expand Down Expand Up @@ -230,5 +247,6 @@ func NewTest() (Interface, error) {
WithLogShowSQL(false),
WithLogSkipNotFound(true),
WithLogSlowThreshold(200*time.Millisecond),
WithTracing(&tracing.Client{Config: tracing.Config{EnableTracing: false}}),
)
}
3 changes: 3 additions & 0 deletions database/database_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"gorm.io/driver/postgres"
"gorm.io/driver/sqlite"
"gorm.io/gorm"

"github.com/go-vela/server/tracing"
)

func TestDatabase_New(t *testing.T) {
Expand Down Expand Up @@ -110,6 +112,7 @@ func TestDatabase_New(t *testing.T) {
WithLogSlowThreshold(test.config.LogSlowThreshold),
WithEncryptionKey(test.config.EncryptionKey),
WithSkipCreation(test.config.SkipCreation),
WithTracing(&tracing.Client{Config: tracing.Config{EnableTracing: false}}),
)

if test.failure {
Expand Down
2 changes: 2 additions & 0 deletions database/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"github.com/go-vela/server/database/testutils"
"github.com/go-vela/server/database/user"
"github.com/go-vela/server/database/worker"
"github.com/go-vela/server/tracing"
"github.com/go-vela/types/constants"
"github.com/go-vela/types/library"
"github.com/go-vela/types/raw"
Expand Down Expand Up @@ -114,6 +115,7 @@ func TestDatabase_Integration(t *testing.T) {
WithDriver(test.config.Driver),
WithEncryptionKey(test.config.EncryptionKey),
WithSkipCreation(test.config.SkipCreation),
WithTracing(&tracing.Client{Config: tracing.Config{EnableTracing: false}}),
)
if err != nil {
t.Errorf("unable to create new database engine for %s: %v", test.name, err)
Expand Down
11 changes: 11 additions & 0 deletions database/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ package database
import (
"context"
"time"

"github.com/go-vela/server/tracing"
)

// EngineOpt represents a configuration option to initialize the database engine.
Expand Down Expand Up @@ -130,6 +132,15 @@ func WithSkipCreation(skipCreation bool) EngineOpt {
}
}

// WithTracing sets the shared tracing config in the database engine.
func WithTracing(tracing *tracing.Client) EngineOpt {
return func(e *engine) error {
e.tracing = tracing

return nil
}
}

// WithContext sets the context in the database engine.
func WithContext(ctx context.Context) EngineOpt {
return func(e *engine) error {
Expand Down
Loading
Loading