Skip to content

Commit ac146b2

Browse files
authored
Merge branch 'main' into main
2 parents 893d05f + 9d3321b commit ac146b2

430 files changed

Lines changed: 45098 additions & 12062 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
---
2+
description: Development workflow, commands, and commit guidelines for Pyroscope
3+
globs:
4+
alwaysApply: false
5+
---
6+
7+
# Development Workflow
8+
9+
## Prerequisites
10+
11+
- Go 1.24+
12+
- Docker
13+
- Node v18
14+
- Yarn v1.22
15+
- All other tools auto-download to `.tmp/bin/`
16+
17+
## Build Commands
18+
19+
```bash
20+
# Build backend
21+
make go/bin
22+
23+
# Run Go tests
24+
make go/test
25+
26+
# Build frontend
27+
yarn install
28+
yarn dev # Dev server on :4041
29+
30+
# Run backend for frontend development
31+
yarn backend:dev # Runs Pyroscope server
32+
33+
# Docker image
34+
make GOOS=linux GOARCH=amd64 docker-image/pyroscope/build
35+
```
36+
37+
## Running Locally
38+
39+
```bash
40+
# Run all components in monolithic mode with embedded Grafana
41+
go run ./cmd/pyroscope --target all,embedded-grafana
42+
# Pyroscope: http://localhost:4040
43+
# Grafana: http://localhost:4041
44+
```
45+
46+
## Code Generation
47+
48+
**CRITICAL**: After changing protobuf, configs, or flags:
49+
50+
```bash
51+
make generate
52+
```
53+
54+
Commit the generated files with your changes.
55+
56+
## Useful Make Targets
57+
58+
```bash
59+
make help # Show all available targets
60+
make lint # Run linters
61+
make go/test # Run Go unit tests
62+
make go/bin # Build binaries
63+
make go/mod # Tidy go modules
64+
make generate # Generate code (protobuf, mocks, etc.)
65+
make docker-image/pyroscope/build # Build Docker image
66+
```
67+
68+
## Commit Guidelines
69+
70+
1. **Atomic Commits**: Each commit should be a logical unit
71+
2. **Commit Messages**: Focus on "why" not just "what"
72+
3. **Generated Code**: Include generated files in the same commit as source changes
73+
4. **Format**: Follow existing commit message style (see `git log --oneline -20`)
74+
75+
## When Working on Features
76+
77+
1. **Read Component Docs**: Check `docs/sources/reference-pyroscope-architecture/components/` for the component you're modifying
78+
2. **Understand the Ring**: If working on write/read path, understand consistent hashing
79+
3. **Multi-tenancy First**: Always consider multi-tenant implications
80+
4. **Check for Similar Code**: Pyroscope is inspired by Cortex/Mimir - similar patterns apply
81+
5. **Test Multi-tenancy**: Test with multiple tenants to catch isolation issues
82+
6. **Profile Your Changes**: Use `go test -bench` and verify performance impact
83+
7. **Update Documentation**: If changing user-facing behavior, update docs
84+
85+
## Documentation Locations
86+
87+
- **User Docs**: `docs/sources/` - Published to grafana.com
88+
- **Contributing**: `docs/internal/contributing/README.md`
89+
- **Component Docs**: `docs/sources/reference-pyroscope-architecture/components/`
90+
91+
## Getting Help
92+
93+
- **Contributing Guide**: `docs/internal/contributing/README.md`
94+
- **Code Comments**: The codebase has extensive comments - read them
95+
- **Git History**: Use `git blame` and `git log` to understand design decisions

.cursor/rules/go-backend.mdc

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
---
2+
description: Go backend coding standards and patterns for Pyroscope
3+
globs:
4+
- "**/*.go"
5+
- "!**/*_test.go"
6+
---
7+
8+
# Go Backend Development
9+
10+
## Import Organization
11+
12+
Always organize imports into three groups separated by blank lines:
13+
14+
```go
15+
import (
16+
// Standard library
17+
"context"
18+
"fmt"
19+
20+
// Third-party packages
21+
"github.com/prometheus/client_golang/prometheus"
22+
"go.uber.org/atomic"
23+
24+
// Internal packages
25+
"github.com/grafana/pyroscope/pkg/model"
26+
"github.com/grafana/pyroscope/pkg/objstore"
27+
)
28+
```
29+
30+
**Don't** add imports within the three import groups (keep them separate).
31+
32+
## Formatting & Linting
33+
34+
- Use `golangci-lint` (run via `make lint`)
35+
- gofmt for formatting
36+
- goimports with `-local github.com/grafana/pyroscope`
37+
- Enabled linters: depguard, goconst, misspell, revive, unconvert, unparam
38+
39+
## Logging
40+
41+
- **Use**: `github.com/go-kit/log`
42+
- **Don't**: `github.com/go-kit/kit/log` (deprecated import path)
43+
- **Don't**: `fmt.Println` for logging
44+
45+
Use structured logging:
46+
47+
```go
48+
import "github.com/go-kit/log/level"
49+
50+
level.Error(logger).Log("msg", "failed to process", "err", err)
51+
level.Info(logger).Log("msg", "processing complete", "count", count)
52+
```
53+
54+
## Error Handling
55+
56+
- Always check errors explicitly
57+
- Wrap errors with context:
58+
59+
```go
60+
if err != nil {
61+
return fmt.Errorf("failed to query: %w", err)
62+
}
63+
```
64+
65+
## Context Usage
66+
67+
- Always pass `context.Context` as the first parameter
68+
- Respect context cancellation in loops and long operations:
69+
70+
```go
71+
func process(ctx context.Context, items []Item) error {
72+
for _, item := range items {
73+
select {
74+
case <-ctx.Done():
75+
return ctx.Err()
76+
default:
77+
}
78+
// process item
79+
}
80+
return nil
81+
}
82+
```
83+
84+
## Multi-tenancy Pattern
85+
86+
All requests must include a tenant ID. Extract from context:
87+
88+
```go
89+
import "github.com/grafana/pyroscope/pkg/tenant"
90+
91+
tenantID, err := tenant.ExtractTenantIDFromContext(ctx)
92+
if err != nil {
93+
return err
94+
}
95+
```
96+
97+
**Never** hardcode tenant IDs.
98+
99+
## Object Storage Pattern
100+
101+
Use the abstract Bucket interface:
102+
103+
```go
104+
import "github.com/grafana/pyroscope/pkg/objstore"
105+
106+
bucket := objstore.NewBucket(cfg)
107+
reader, err := bucket.Get(ctx, "path/to/object")
108+
```
109+
110+
## Configuration Pattern
111+
112+
Use `github.com/grafana/dskit` for configuration:
113+
114+
```go
115+
type Config struct {
116+
ListenPort int `yaml:"listen_port"`
117+
}
118+
119+
func (cfg *Config) RegisterFlags(f *flag.FlagSet) {
120+
f.IntVar(&cfg.ListenPort, "server.http-listen-port", 4040, "HTTP listen port")
121+
}
122+
```
123+
124+
## Performance Considerations
125+
126+
1. **Minimize Allocations in Hot Paths**:
127+
- Reuse buffers with `sync.Pool`
128+
- Avoid string concatenation in loops
129+
- Use `strings.Builder` for string building
130+
131+
2. **Concurrency**:
132+
- Use worker pools for bounded concurrency
133+
- Prefer channels for coordination over mutexes when possible
134+
- Don't create unbounded goroutines - use worker pools or semaphores
135+
136+
3. **Profile Your Changes**:
137+
```bash
138+
go test -cpuprofile=cpu.prof -memprofile=mem.prof -bench=.
139+
go tool pprof cpu.prof
140+
```
141+
142+
## Code Generation
143+
144+
**IMPORTANT**: After changing protobuf, configs, or flags:
145+
```bash
146+
make generate
147+
```
148+
Commit the generated files with your changes.
149+
150+
## Security
151+
152+
1. **Input Validation**: Always validate and sanitize user input
153+
2. **Path Traversal**: Validate object keys before storage operations
154+
3. **Rate Limiting**: Distributor implements per-tenant rate limiting

.cursor/rules/go-testing.mdc

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
---
2+
description: Go testing standards and patterns for Pyroscope
3+
globs:
4+
- "**/*_test.go"
5+
---
6+
7+
# Go Testing Standards
8+
9+
## Test File Naming
10+
11+
- Test files: `*_test.go`
12+
- Test function naming: `TestFunctionName` or `TestComponentName_Method`
13+
14+
## Test Structure
15+
16+
Use table-driven tests for multiple cases:
17+
18+
```go
19+
func TestDistributor_Push(t *testing.T) {
20+
t.Parallel()
21+
22+
tests := []struct {
23+
name string
24+
input *pushv1.PushRequest
25+
wantErr bool
26+
}{
27+
{name: "valid request", input: validRequest(), wantErr: false},
28+
{name: "invalid tenant", input: invalidRequest(), wantErr: true},
29+
}
30+
31+
for _, tt := range tests {
32+
t.Run(tt.name, func(t *testing.T) {
33+
d := setupDistributor(t)
34+
err := d.Push(context.Background(), tt.input)
35+
if tt.wantErr {
36+
require.Error(t, err)
37+
} else {
38+
require.NoError(t, err)
39+
}
40+
})
41+
}
42+
}
43+
```
44+
45+
## Assertions
46+
47+
- Use `require` for fatal assertions (test stops on failure)
48+
- Use `assert` for non-fatal assertions (test continues)
49+
50+
```go
51+
import (
52+
"github.com/stretchr/testify/assert"
53+
"github.com/stretchr/testify/require"
54+
)
55+
56+
// Fatal - stops test immediately
57+
require.NoError(t, err)
58+
require.NotNil(t, result)
59+
60+
// Non-fatal - continues test
61+
assert.Equal(t, expected, actual)
62+
```
63+
64+
## Test Organization
65+
66+
1. **Subtests**: Use `t.Run()` for grouping related cases
67+
2. **Parallel Tests**: Use `t.Parallel()` when tests are independent
68+
3. **Cleanup**: Always use `t.Cleanup()` for resource cleanup
69+
70+
```go
71+
func TestComponent(t *testing.T) {
72+
t.Parallel()
73+
74+
resource := createResource(t)
75+
t.Cleanup(func() {
76+
resource.Close()
77+
})
78+
79+
t.Run("subtest", func(t *testing.T) {
80+
// test logic
81+
})
82+
}
83+
```
84+
85+
## Test Data
86+
87+
- Store test data in `testdata/` directories
88+
- Use relative paths from the test file
89+
90+
## Integration Tests
91+
92+
Use build tags for integration tests:
93+
94+
```go
95+
//go:build integration
96+
97+
package mypackage_test
98+
99+
func TestIntegration(t *testing.T) {
100+
// ...
101+
}
102+
```
103+
104+
## Mocking
105+
106+
- Use `mockery` for generating mocks from interfaces
107+
- Place mocks in appropriate locations based on scope
108+
109+
## Multi-tenancy Testing
110+
111+
**Always test with multiple tenants** to catch isolation issues:
112+
113+
```go
114+
func TestMultiTenant(t *testing.T) {
115+
tenants := []string{"tenant-a", "tenant-b"}
116+
117+
for _, tenant := range tenants {
118+
t.Run(tenant, func(t *testing.T) {
119+
ctx := tenant.InjectTenantID(context.Background(), tenant)
120+
// verify tenant isolation
121+
})
122+
}
123+
}
124+
```
125+
126+
## Running Tests
127+
128+
```bash
129+
# Run all tests
130+
make go/test
131+
132+
# Run specific package tests
133+
go test ./pkg/distributor/...
134+
135+
# Run with race detector
136+
go test -race ./...
137+
138+
# Run benchmarks
139+
go test -bench=. ./pkg/...
140+
```

0 commit comments

Comments
 (0)