github.com/grafana/pyroscope@v1.18.0/cmd/profilecli/main.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  
    10  	"github.com/go-kit/log"
    11  	"github.com/go-kit/log/level"
    12  	"github.com/prometheus/common/version"
    13  	"gopkg.in/alecthomas/kingpin.v2"
    14  
    15  	phlarecontext "github.com/grafana/pyroscope/pkg/pyroscope/context"
    16  	_ "github.com/grafana/pyroscope/pkg/util/build"
    17  )
    18  
    19  var cfg struct {
    20  	verbose bool
    21  	blocks  struct {
    22  		path               string
    23  		restoreMissingMeta bool
    24  		compact            struct {
    25  			src    string
    26  			dst    string
    27  			shards int
    28  		}
    29  	}
    30  }
    31  
    32  var (
    33  	consoleOutput = os.Stderr
    34  	logger        = log.NewLogfmtLogger(consoleOutput)
    35  )
    36  
    37  func main() {
    38  	ctx := phlarecontext.WithLogger(context.Background(), logger)
    39  	ctx = withOutput(ctx, os.Stdout)
    40  
    41  	app := kingpin.New(filepath.Base(os.Args[0]), "Tooling for Grafana Pyroscope, the continuous profiling aggregation system.").UsageWriter(os.Stdout)
    42  	app.Version(version.Print("profilecli"))
    43  	app.HelpFlag.Short('h')
    44  	app.Flag("verbose", "Enable verbose logging.").Short('v').Default("0").BoolVar(&cfg.verbose)
    45  
    46  	adminCmd := app.Command("admin", "Administrative tasks for Pyroscope cluster operators.")
    47  
    48  	blocksCmd := adminCmd.Command("blocks", "Operate on Grafana Pyroscope's blocks.")
    49  	blocksCmd.Flag("path", "Path to blocks directory").Default("./data/anonymous/local").StringVar(&cfg.blocks.path)
    50  
    51  	blocksListCmd := blocksCmd.Command("list", "List blocks.")
    52  	blocksListCmd.Flag("restore-missing-meta", "").Default("false").BoolVar(&cfg.blocks.restoreMissingMeta)
    53  
    54  	blocksCompactCmd := blocksCmd.Command("compact", "Compact blocks.")
    55  	blocksCompactCmd.Arg("from", "The source input blocks to compact.").Required().ExistingDirVar(&cfg.blocks.compact.src)
    56  	blocksCompactCmd.Arg("dest", "The destination where compacted blocks should be stored.").Required().StringVar(&cfg.blocks.compact.dst)
    57  	blocksCompactCmd.Flag("shards", "The amount of shards to split output blocks into.").Default("0").IntVar(&cfg.blocks.compact.shards)
    58  
    59  	blocksQueryCmd := blocksCmd.Command("query", "Query on local/remote blocks.")
    60  	blocksQuerySeriesCmd := blocksQueryCmd.Command("series", "Request series labels on local/remote blocks.")
    61  	blocksQuerySeriesParams := addBlocksQuerySeriesParams(blocksQuerySeriesCmd)
    62  	blocksQueryProfileCmd := blocksQueryCmd.Command("profile", "Request merged profile on local/remote block.").Alias("merge")
    63  	blocksQueryProfileParams := addBlocksQueryProfileParams(blocksQueryProfileCmd)
    64  
    65  	parquetCmd := adminCmd.Command("parquet", "Operate on a Parquet file.")
    66  	parquetInspectCmd := parquetCmd.Command("inspect", "Inspect a parquet file's structure.")
    67  	parquetInspectFiles := parquetInspectCmd.Arg("file", "parquet file path").Required().ExistingFiles()
    68  
    69  	tsdbCmd := adminCmd.Command("tsdb", "Operate on a TSDB index file.")
    70  	tsdbSeriesCmd := tsdbCmd.Command("series", "dump series in an TSDB index file.")
    71  	tsdbSeriesFiles := tsdbSeriesCmd.Arg("file", "tsdb file path").Required().ExistingFiles()
    72  
    73  	queryCmd := app.Command("query", "Query profile store.")
    74  	queryProfileCmd := queryCmd.Command("profile", "Request merged profile.").Alias("merge")
    75  	queryProfileOutput := queryProfileCmd.Flag("output", "How to output the result, examples: console, raw, pprof=./my.pprof").Default("console").String()
    76  	queryProfileParams := addQueryProfileParams(queryProfileCmd)
    77  	queryGoPGOCmd := queryCmd.Command("go-pgo", "Request profile for Go PGO.")
    78  	queryGoPGOOutput := queryGoPGOCmd.Flag("output", "How to output the result, examples: console, raw, pprof=./my.pprof").Default("pprof=./default.pgo").String()
    79  	queryGoPGOParams := addQueryGoPGOParams(queryGoPGOCmd)
    80  	querySeriesCmd := queryCmd.Command("series", "Request series labels.")
    81  	querySeriesParams := addQuerySeriesParams(querySeriesCmd)
    82  	queryLabelValuesCardinalityCmd := queryCmd.Command("label-values-cardinality", "Request label values cardinality.")
    83  	queryLabelValuesCardinalityParams := addQueryLabelValuesCardinalityParams(queryLabelValuesCardinalityCmd)
    84  
    85  	queryTracerCmd := app.Command("query-tracer", "Analyze query traces.")
    86  	queryTracerParams := addQueryTracerParams(queryTracerCmd)
    87  
    88  	uploadCmd := app.Command("upload", "Upload profile(s).")
    89  	uploadParams := addUploadParams(uploadCmd)
    90  
    91  	canaryExporterCmd := app.Command("canary-exporter", "Run the canary exporter.")
    92  	canaryExporterParams := addCanaryExporterParams(canaryExporterCmd)
    93  
    94  	bucketCmd := adminCmd.Command("bucket", "Run the bucket visualization tool.")
    95  	bucketWebCmd := bucketCmd.Command("web", "Run the web tool for visualizing blocks in object-store buckets.")
    96  	bucketWebParams := addBucketWebToolParams(bucketWebCmd)
    97  
    98  	bucketListV2Cmd := bucketCmd.Command("list-v2-blocks", "List Pyroscope v2 segments and blocks in object-store buckets.")
    99  	bucketListV2Params := addBucketParams(bucketListV2Cmd)
   100  
   101  	bucketInspectV2Cmd := bucketCmd.Command("inspect-v2-blocks", "Inspect Pyroscope v2 segments and blocks in object-store buckets.")
   102  	bucketInspectV2Params := addBucketParams(bucketInspectV2Cmd)
   103  	bucketInspectV2Paths := bucketInspectV2Cmd.Arg("path", "block paths").Required().Strings()
   104  
   105  	readyCmd := app.Command("ready", "Check Pyroscope health.")
   106  	readyParams := addReadyParams(readyCmd)
   107  
   108  	sourceCodeCmd := app.Command("source-code", "Operations on source code mappings and configurations.")
   109  	sourceCodeCoverageCmd := sourceCodeCmd.Command("coverage", "Measure the coverage of .pyroscope.yaml source code mappings for translating function names/paths from a pprof profile to VCS source files.")
   110  	sourceCodeCoverageParams := addSourceCodeCoverageParams(sourceCodeCoverageCmd)
   111  
   112  	raftCmd := adminCmd.Command("raft", "Operate on Raft cluster.")
   113  	raftInfoCmd := raftCmd.Command("info", "Print info about a Raft node.")
   114  	raftInfoParams := addRaftInfoParams(raftInfoCmd)
   115  
   116  	v2MigrationCmd := adminCmd.Command("v2-migration", "Operation to aid the v1 to v2 storage migration.")
   117  	v2MigrationBucketCleanupCmd := v2MigrationCmd.Command("bucket-cleanup", "Clean up v1 artificats from data bucket.")
   118  	v2MigrationBucketCleanupParams := addV2MigrationBackupCleanupParam(v2MigrationBucketCleanupCmd)
   119  
   120  	// parse command line arguments
   121  	parsedCmd := kingpin.MustParse(app.Parse(os.Args[1:]))
   122  
   123  	// enable verbose logging if requested
   124  	if !cfg.verbose {
   125  		logger = level.NewFilter(logger, level.AllowInfo())
   126  	}
   127  
   128  	switch parsedCmd {
   129  	case blocksListCmd.FullCommand():
   130  		os.Exit(checkError(blocksList(ctx)))
   131  	case parquetInspectCmd.FullCommand():
   132  		for _, file := range *parquetInspectFiles {
   133  			if err := parquetInspect(ctx, file); err != nil {
   134  				os.Exit(checkError(err))
   135  			}
   136  		}
   137  	case tsdbSeriesCmd.FullCommand():
   138  		for _, file := range *tsdbSeriesFiles {
   139  			if err := tsdbSeries(ctx, file); err != nil {
   140  				os.Exit(checkError(err))
   141  			}
   142  		}
   143  	case queryProfileCmd.FullCommand():
   144  		if err := queryProfile(ctx, queryProfileParams, *queryProfileOutput); err != nil {
   145  			os.Exit(checkError(err))
   146  		}
   147  	case queryGoPGOCmd.FullCommand():
   148  		if err := queryGoPGO(ctx, queryGoPGOParams, *queryGoPGOOutput); err != nil {
   149  			os.Exit(checkError(err))
   150  		}
   151  	case querySeriesCmd.FullCommand():
   152  		if err := querySeries(ctx, querySeriesParams); err != nil {
   153  			os.Exit(checkError(err))
   154  		}
   155  
   156  	case blocksQuerySeriesCmd.FullCommand():
   157  		if err := blocksQuerySeries(ctx, blocksQuerySeriesParams); err != nil {
   158  			os.Exit(checkError(err))
   159  		}
   160  	case blocksQueryProfileCmd.FullCommand():
   161  		if err := blocksQueryProfile(ctx, blocksQueryProfileParams); err != nil {
   162  			os.Exit(checkError(err))
   163  		}
   164  
   165  	case queryLabelValuesCardinalityCmd.FullCommand():
   166  		if err := queryLabelValuesCardinality(ctx, queryLabelValuesCardinalityParams); err != nil {
   167  			os.Exit(checkError(err))
   168  		}
   169  
   170  	case queryTracerCmd.FullCommand():
   171  		if err := queryTracer(ctx, queryTracerParams); err != nil {
   172  			os.Exit(checkError(err))
   173  		}
   174  
   175  	case uploadCmd.FullCommand():
   176  		if err := upload(ctx, uploadParams); err != nil {
   177  			os.Exit(checkError(err))
   178  		}
   179  	case canaryExporterCmd.FullCommand():
   180  		if err := newCanaryExporter(canaryExporterParams).run(ctx); err != nil {
   181  			os.Exit(checkError(err))
   182  		}
   183  	case bucketWebCmd.FullCommand():
   184  		if err := newBucketWebTool(bucketWebParams).run(ctx); err != nil {
   185  			os.Exit(checkError(err))
   186  		}
   187  	case bucketListV2Cmd.FullCommand():
   188  		if err := bucketListV2(ctx, bucketListV2Params); err != nil {
   189  			os.Exit(checkError(err))
   190  		}
   191  	case bucketInspectV2Cmd.FullCommand():
   192  		if err := bucketInspectV2(ctx, bucketInspectV2Params, *bucketInspectV2Paths); err != nil {
   193  			os.Exit(checkError(err))
   194  		}
   195  	case blocksCompactCmd.FullCommand():
   196  		if err := blocksCompact(ctx, cfg.blocks.compact.src, cfg.blocks.compact.dst, cfg.blocks.compact.shards); err != nil {
   197  			os.Exit(checkError(err))
   198  		}
   199  	case readyCmd.FullCommand():
   200  		if err := ready(ctx, readyParams); err != nil {
   201  			os.Exit(checkError(err))
   202  		}
   203  	case sourceCodeCoverageCmd.FullCommand():
   204  		if err := sourceCodeCoverage(ctx, sourceCodeCoverageParams); err != nil {
   205  			os.Exit(checkError(err))
   206  		}
   207  	case raftInfoCmd.FullCommand():
   208  		if err := raftInfo(ctx, raftInfoParams); err != nil {
   209  			os.Exit(checkError(err))
   210  		}
   211  	case v2MigrationBucketCleanupCmd.FullCommand():
   212  		if err := v2MigrationBucketCleanup(ctx, v2MigrationBucketCleanupParams); err != nil {
   213  			os.Exit(checkError(err))
   214  		}
   215  	default:
   216  		level.Error(logger).Log("msg", "unknown command", "cmd", parsedCmd)
   217  	}
   218  }
   219  
   220  func checkError(err error) int {
   221  	switch err {
   222  	case nil:
   223  		return 0
   224  	case notReadyErr:
   225  		// The reason for the failed ready is already logged, so just exit with
   226  		// an error code.
   227  	default:
   228  		fmt.Fprintf(os.Stderr, "error: %v\n", err)
   229  	}
   230  	return 1
   231  }
   232  
   233  type contextKey uint8
   234  
   235  const (
   236  	contextKeyOutput contextKey = iota
   237  )
   238  
   239  func withOutput(ctx context.Context, w io.Writer) context.Context {
   240  	return context.WithValue(ctx, contextKeyOutput, w)
   241  }
   242  
   243  func output(ctx context.Context) io.Writer {
   244  	if w, ok := ctx.Value(contextKeyOutput).(io.Writer); ok {
   245  		return w
   246  	}
   247  	return os.Stdout
   248  }