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 }