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

Added query blocks cli command #876

Merged
merged 5 commits into from
Aug 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## main / unreleased

* [BUGFIX] Update port spec for GCS docker-compose example [#869](https://github.com/grafana/tempo/pull/869) (@zalegrala)
* [ENHANCEMENT] Added "query blocks" cli option. [#876](https://github.com/grafana/tempo/pull/876) (@joe-elliott)

## v1.1.0-rc.0 / 2021-08-11

Expand Down
162 changes: 162 additions & 0 deletions cmd/tempo-cli/cmd-query-blocks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package main

import (
"context"
"encoding/json"
"fmt"
"strconv"

"github.com/google/uuid"
"github.com/grafana/tempo/pkg/boundedwaitgroup"
"github.com/grafana/tempo/pkg/model"
"github.com/grafana/tempo/pkg/tempopb"
"github.com/grafana/tempo/pkg/util"
"github.com/grafana/tempo/tempodb/backend"
"github.com/grafana/tempo/tempodb/encoding"
"github.com/grafana/tempo/tempodb/encoding/common"
)

type queryResults struct {
blockID uuid.UUID
trace *tempopb.Trace
}

type queryBlocksCmd struct {
backendOptions

TraceID string `arg:"" help:"trace ID to retrieve"`
TenantID string `arg:"" help:"tenant ID to search"`
}

func (cmd *queryBlocksCmd) Run(ctx *globalOptions) error {
r, c, err := loadBackend(&cmd.backendOptions, ctx)
if err != nil {
return err
}

id, err := util.HexStringToTraceID(cmd.TraceID)
if err != nil {
return err
}

results, err := queryBucket(context.Background(), r, c, cmd.TenantID, id)
if err != nil {
return err
}

var combinedTrace *tempopb.Trace

fmt.Println()
for _, result := range results {
fmt.Println(result.blockID, ":")

jsonBytes, err := json.Marshal(result.trace)
if err != nil {
fmt.Println("failed to marshal to json: ", err)
continue
}

fmt.Println(string(jsonBytes))
combinedTrace, _, _, _ = model.CombineTraceProtos(result.trace, combinedTrace)
}

fmt.Println("combined:")
jsonBytes, err := json.Marshal(combinedTrace)
if err != nil {
fmt.Println("failed to marshal to json: ", err)
return nil
}
fmt.Println(string(jsonBytes))
return nil
}

func queryBucket(ctx context.Context, r backend.Reader, c backend.Compactor, tenantID string, traceID common.ID) ([]queryResults, error) {
blockIDs, err := r.Blocks(context.Background(), tenantID)
if err != nil {
return nil, err
}

fmt.Println("total blocks: ", len(blockIDs))

// Load in parallel
wg := boundedwaitgroup.New(20)
resultsCh := make(chan queryResults, len(blockIDs))

for blockNum, id := range blockIDs {
wg.Add(1)

go func(blockNum2 int, id2 uuid.UUID) {
defer wg.Done()

// search here
q, err := queryBlock(ctx, r, c, blockNum2, id2, tenantID, traceID)
if err != nil {
fmt.Println("Error querying block:", err)
return
}

if q != nil {
resultsCh <- *q
}
}(blockNum, id)
}

wg.Wait()
close(resultsCh)

results := make([]queryResults, 0)
for q := range resultsCh {
results = append(results, q)
}

return results, nil
}

func queryBlock(ctx context.Context, r backend.Reader, c backend.Compactor, blockNum int, id uuid.UUID, tenantID string, traceID common.ID) (*queryResults, error) {
fmt.Print(".")
if blockNum%100 == 0 {
fmt.Print(strconv.Itoa(blockNum))
}

meta, err := r.BlockMeta(context.Background(), id, tenantID)
if err != nil && err != backend.ErrDoesNotExist {
return nil, err
}

if err == backend.ErrDoesNotExist {
compactedMeta, err := c.CompactedBlockMeta(id, tenantID)
if err != nil && err != backend.ErrDoesNotExist {
return nil, err
}

if compactedMeta == nil {
return nil, fmt.Errorf("compacted meta nil?")
}

meta = &compactedMeta.BlockMeta
}

block, err := encoding.NewBackendBlock(meta, r)
if err != nil {
return nil, err
}

obj, err := block.Find(ctx, traceID)
if err != nil {
return nil, err
}

if obj == nil {
return nil, nil
}

trace, err := model.Unmarshal(obj, meta.DataEncoding)
if err != nil {
return nil, err
}

return &queryResults{
blockID: id,
trace: trace,
}, nil
}
5 changes: 4 additions & 1 deletion cmd/tempo-cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ var cli struct {
Index viewIndexCmd `cmd:"" help:"View contents of block index"`
} `cmd:""`

Query queryCmd `cmd:"" help:"query tempo api"`
Query struct {
API queryCmd `cmd:"" help:"query tempo http api"`
Blocks queryBlocksCmd `cmd:"" help:"query for a traceid directly from backend blocks"`
} `cmd:""`
}

func main() {
Expand Down
25 changes: 22 additions & 3 deletions docs/tempo/website/operations/tempo_cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ The backend can be configured in a few ways:

Each option applies only to the command in which it is used. For example, `--backend <value>` does not permanently change where Tempo stores data. It only changes it for command in which you apply the option.

## Query Command
## Query API Command
Call the tempo API and retrieve a trace by ID.
```bash
tempo-cli query <api-endpoint> <trace-id>
tempo-cli query api <api-endpoint> <trace-id>
```

Arguments:
Expand All @@ -63,7 +63,26 @@ Options:

**Example:**
```bash
tempo-cli query http://tempo:3200 f1cfe82a8eef933b
tempo-cli query api http://tempo:3200 f1cfe82a8eef933b
```

## Query Blocks Command
Iterate over all backend blocks and dump all data found for a given trace id.
```bash
tempo-cli query blocks <trace-id> <tenant-id>
```
**Note:** can be intense as it downloads every bloom filter and some percentage of indexes/trace data.

Arguments:
- `trace-id` Trace ID as a hexadecimal string.
- `tenant-id` Tenant to search.

Options:
See backend options above.

**Example:**
```bash
tempo-cli query blocks f1cfe82a8eef933b single-tenant
```

## List Blocks
Expand Down
4 changes: 2 additions & 2 deletions pkg/util/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ func ParseTraceID(r *http.Request) ([]byte, error) {
return nil, fmt.Errorf("please provide a traceID")
}

byteID, err := hexStringToTraceID(traceID)
byteID, err := HexStringToTraceID(traceID)
if err != nil {
return nil, err
}

return byteID, nil
}

func hexStringToTraceID(id string) ([]byte, error) {
func HexStringToTraceID(id string) ([]byte, error) {
// The encoding/hex package does not handle non-hex characters.
// Ensure the ID has only the proper characters
for pos, idChar := range strings.Split(id, "") {
Expand Down
2 changes: 1 addition & 1 deletion pkg/util/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func TestHexStringToTraceID(t *testing.T) {

for _, tt := range tc {
t.Run(tt.id, func(t *testing.T) {
actual, err := hexStringToTraceID(tt.id)
actual, err := HexStringToTraceID(tt.id)

if tt.expectError != nil {
assert.Equal(t, tt.expectError, err)
Expand Down