github.com/grafana/pyroscope@v1.18.0/pkg/ingester/pyroscope/ingest_adapter.go (about) 1 package pyroscope 2 3 import ( 4 "context" 5 "fmt" 6 "net/http" 7 "strings" 8 "time" 9 10 "github.com/go-kit/log/level" 11 12 "github.com/grafana/pyroscope/pkg/distributor/model" 13 "github.com/grafana/pyroscope/pkg/tenant" 14 15 "connectrpc.com/connect" 16 "google.golang.org/protobuf/proto" 17 18 "github.com/go-kit/log" 19 "github.com/google/uuid" 20 "github.com/prometheus/prometheus/model/labels" 21 22 pushv1 "github.com/grafana/pyroscope/api/gen/proto/go/push/v1" 23 typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1" 24 phlaremodel "github.com/grafana/pyroscope/pkg/model" 25 "github.com/grafana/pyroscope/pkg/og/ingestion" 26 "github.com/grafana/pyroscope/pkg/og/storage" 27 "github.com/grafana/pyroscope/pkg/og/storage/tree" 28 ) 29 30 type PushService interface { 31 Push(ctx context.Context, req *connect.Request[pushv1.PushRequest]) (*connect.Response[pushv1.PushResponse], error) 32 PushBatch(ctx context.Context, req *model.PushRequest) error 33 } 34 35 type Limits interface { 36 MaxProfileSizeBytes(tenantID string) int 37 } 38 39 func NewPyroscopeIngestHandler(svc PushService, limits Limits, logger log.Logger) http.Handler { 40 return NewIngestHandler( 41 logger, 42 &pyroscopeIngesterAdapter{svc: svc, limits: limits, log: logger}, 43 ) 44 } 45 46 type pyroscopeIngesterAdapter struct { 47 svc PushService 48 limits Limits 49 log log.Logger 50 } 51 52 func (p *pyroscopeIngesterAdapter) Ingest(ctx context.Context, in *ingestion.IngestInput) error { 53 pprofable, ok := in.Profile.(ingestion.ParseableToPprof) 54 if ok { 55 return p.parseToPprof(ctx, in, pprofable) 56 } else { 57 return in.Profile.Parse(ctx, p, p, in.Metadata) 58 } 59 } 60 61 const ( 62 metricProcessCPU = "process_cpu" 63 metricMemory = "memory" 64 metricMutex = "mutex" 65 metricBlock = "block" 66 metricWall = "wall" 67 stUnitCount = "count" 68 stTypeSamples = "samples" 69 stTypeCPU = "cpu" 70 stTypeWall = "wall" 71 stUnitBytes = "bytes" 72 stTypeContentions = "contentions" 73 stTypeDelay = "delay" 74 stUnitNanos = "nanoseconds" 75 ) 76 77 func (p *pyroscopeIngesterAdapter) Put(ctx context.Context, pi *storage.PutInput) error { 78 if pi.LabelSet.HasProfileID() { 79 return nil 80 } 81 metric, stType, stUnit, app, err := convertMetadata(pi) 82 if err != nil { 83 return connect.NewError( 84 connect.CodeInvalidArgument, 85 fmt.Errorf("pyroscopeIngesterAdapter failed to convert metadata: %w", err), 86 ) 87 } 88 mdata := &tree.PprofMetadata{ 89 Type: stType, 90 Unit: stUnit, 91 StartTime: pi.StartTime, 92 } 93 if pi.SampleRate != 0 && (metric == metricWall || metric == metricProcessCPU) { 94 period := time.Second.Nanoseconds() / int64(pi.SampleRate) 95 mdata.Period = period 96 mdata.PeriodType = stTypeCPU 97 mdata.PeriodUnit = stUnitNanos 98 if metric == metricWall { 99 mdata.Type = stTypeWall 100 } else { 101 mdata.Type = stTypeCPU 102 } 103 mdata.Unit = stUnitNanos 104 pi.Val.Scale(uint64(period)) 105 } 106 pprof := pi.Val.Pprof(mdata) 107 b, err := proto.Marshal(pprof) 108 if err != nil { 109 return connect.NewError( 110 connect.CodeInvalidArgument, 111 fmt.Errorf("pyroscopeIngesterAdapter failed to marshal pprof: %w", err), 112 ) 113 } 114 req := &pushv1.PushRequest{} 115 series := &pushv1.RawProfileSeries{ 116 Labels: make([]*typesv1.LabelPair, 0, 3+len(pi.LabelSet.Labels())), 117 } 118 series.Labels = append(series.Labels, &typesv1.LabelPair{ 119 Name: labels.MetricName, 120 Value: metric, 121 }, &typesv1.LabelPair{ 122 Name: phlaremodel.LabelNameDelta, 123 Value: "false", 124 }) 125 if pi.SpyName != "" { 126 series.Labels = append(series.Labels, &typesv1.LabelPair{ 127 Name: phlaremodel.LabelNamePyroscopeSpy, 128 Value: pi.SpyName, 129 }) 130 } 131 hasServiceName := false 132 for k, v := range pi.LabelSet.Labels() { 133 if !phlaremodel.IsLabelAllowedForIngestion(k) { 134 continue 135 } 136 if k == "service_name" { 137 hasServiceName = true 138 } 139 series.Labels = append(series.Labels, &typesv1.LabelPair{ 140 Name: k, 141 Value: v, 142 }) 143 } 144 // If service_name is not present, use app_name as the service_name. 145 if !hasServiceName { 146 series.Labels = append(series.Labels, &typesv1.LabelPair{ 147 Name: "service_name", 148 Value: app, 149 }) 150 } else { 151 // Check if app_name already exists 152 hasAppName := false 153 for _, label := range series.Labels { 154 if label.Name == "app_name" { 155 hasAppName = true 156 break 157 } 158 } 159 160 if !hasAppName { 161 series.Labels = append(series.Labels, &typesv1.LabelPair{ 162 Name: "app_name", 163 Value: app, 164 }) 165 } 166 } 167 series.Samples = []*pushv1.RawSample{{ 168 RawProfile: b, 169 ID: uuid.New().String(), 170 }} 171 req.Series = append(req.Series, series) 172 _, err = p.svc.Push(ctx, connect.NewRequest(req)) 173 if err != nil { 174 return fmt.Errorf("pyroscopeIngesterAdapter failed to push: %w", err) 175 } 176 return nil 177 } 178 179 func (p *pyroscopeIngesterAdapter) Evaluate(input *storage.PutInput) (storage.SampleObserver, bool) { 180 return nil, false // noop 181 } 182 183 func (p *pyroscopeIngesterAdapter) parseToPprof( 184 ctx context.Context, 185 in *ingestion.IngestInput, 186 pprofable ingestion.ParseableToPprof, 187 ) error { 188 plainReq, err := pprofable.ParseToPprof(ctx, in.Metadata, p.limits) 189 if err != nil { 190 return fmt.Errorf("parsing IngestInput-pprof failed %w", err) 191 } 192 if len(plainReq.Series) == 0 { 193 tenantID, _ := tenant.ExtractTenantIDFromContext(ctx) 194 _ = level.Debug(p.log).Log("msg", "empty profile", 195 "application", in.Metadata.LabelSet.ServiceName(), 196 "orgID", tenantID) 197 return nil 198 } 199 err = p.svc.PushBatch(ctx, plainReq) 200 if err != nil { 201 return fmt.Errorf("pushing IngestInput-pprof failed %w", err) 202 } 203 return nil 204 } 205 206 func convertMetadata(pi *storage.PutInput) (metricName, stType, stUnit, app string, err error) { 207 app = pi.LabelSet.ServiceName() 208 parts := strings.Split(app, ".") 209 if len(parts) <= 1 { 210 stType = stTypeCPU 211 } else { 212 stType = parts[len(parts)-1] 213 app = strings.Join(parts[:len(parts)-1], ".") 214 } 215 switch stType { 216 case stTypeCPU: 217 metricName = metricProcessCPU 218 stType = stTypeSamples 219 stUnit = stUnitCount 220 case "wall": 221 metricName = metricWall 222 stType = stTypeSamples 223 stUnit = stUnitCount 224 case "inuse_objects": 225 metricName = metricMemory 226 stUnit = stUnitCount 227 case "inuse_space": 228 metricName = metricMemory 229 stUnit = stUnitBytes 230 case "alloc_objects": 231 metricName = metricMemory 232 stUnit = stUnitCount 233 case "alloc_space": 234 metricName = metricMemory 235 stUnit = stUnitBytes 236 case "goroutines": 237 metricName = "goroutine" 238 stUnit = stUnitCount 239 case "mutex_count": 240 stType = stTypeContentions 241 stUnit = stUnitCount 242 metricName = metricMutex 243 case "mutex_duration": 244 stType = stTypeDelay 245 stUnit = stUnitNanos 246 metricName = metricMutex 247 case "block_count": 248 stType = stTypeContentions 249 stUnit = stUnitCount 250 metricName = metricBlock 251 case "block_duration": 252 stType = stTypeDelay 253 stUnit = stUnitNanos 254 metricName = metricBlock 255 case "itimer": 256 metricName = metricProcessCPU 257 stType = stTypeSamples 258 stUnit = stUnitCount 259 case "alloc_in_new_tlab_objects": 260 metricName = metricMemory 261 stType = "alloc_in_new_tlab_objects" 262 stUnit = stUnitCount 263 case "alloc_in_new_tlab_bytes": 264 metricName = metricMemory 265 stType = "alloc_in_new_tlab_bytes" 266 stUnit = stUnitBytes 267 case "alloc_outside_tlab_objects": 268 metricName = metricMemory 269 stType = "alloc_outside_tlab_objects" 270 stUnit = stUnitCount 271 case "alloc_outside_tlab_bytes": 272 metricName = metricMemory 273 stType = "alloc_outside_tlab_bytes" 274 stUnit = stUnitBytes 275 case "lock_count": 276 stType = stTypeContentions 277 stUnit = stUnitCount 278 metricName = metricBlock 279 case "lock_duration": 280 stType = stTypeDelay 281 stUnit = stUnitNanos 282 metricName = metricBlock 283 case "live": 284 metricName = metricMemory 285 stType = "live" 286 stUnit = stUnitCount 287 case "exceptions": 288 metricName = "exceptions" 289 stType = stTypeSamples 290 stUnit = stUnitCount 291 case "seconds", "nanoseconds", "microseconds", "milliseconds": 292 metricName = metricWall 293 stType = stTypeSamples 294 stUnit = stUnitCount 295 case "bytes": 296 metricName = metricMemory 297 stType = stTypeSamples 298 stUnit = stUnitBytes 299 default: 300 err = fmt.Errorf("unknown profile type: %s", stType) 301 } 302 303 return metricName, stType, stUnit, app, err 304 }