github.com/alibaba/ilogtail/pkg@v0.0.0-20250526110833-c53b480d046c/helper/profile/pyroscope/jfr/jfr.go (about) 1 // Copyright 2023 iLogtail Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package jfr 16 17 import ( 18 "bytes" 19 "context" 20 "encoding/json" 21 "fmt" 22 "io" 23 "mime/multipart" 24 "strconv" 25 "strings" 26 27 "github.com/pyroscope-io/pyroscope/pkg/util/form" 28 "google.golang.org/protobuf/proto" 29 30 "github.com/alibaba/ilogtail/pkg/helper/profile" 31 "github.com/alibaba/ilogtail/pkg/protocol" 32 ) 33 34 type RawProfile struct { 35 RawData []byte 36 FormDataContentType string 37 38 logs []*protocol.Log // v1 result 39 } 40 41 func NewRawProfile(data []byte, format string) *RawProfile { 42 return &RawProfile{ 43 RawData: data, 44 FormDataContentType: format, 45 } 46 } 47 48 func (r *RawProfile) Parse(ctx context.Context, meta *profile.Meta, tags map[string]string) (logs []*protocol.Log, err error) { 49 reader, labels, err := r.extractProfileRaw() 50 if err != nil { 51 return nil, err 52 } 53 if err := r.ParseJFR(ctx, meta, reader, labels, r.extractProfileV1(meta, tags)); err != nil { 54 return nil, err 55 } 56 logs = r.logs 57 r.logs = nil 58 return 59 } 60 61 func (r *RawProfile) extractProfileV1(meta *profile.Meta, tags map[string]string) profile.CallbackFunc { 62 profileID := profile.GetProfileID(meta) 63 return func(id uint64, stack *profile.Stack, vals []uint64, types, units, aggs []string, startTime, endTime int64, labels map[string]string) { 64 var content []*protocol.Log_Content 65 for k, v := range tags { 66 labels[k] = v 67 } 68 b, _ := json.Marshal(labels) 69 content = append(content, 70 &protocol.Log_Content{ 71 Key: "name", 72 Value: stack.Name, 73 }, 74 &protocol.Log_Content{ 75 Key: "stack", 76 Value: strings.Join(stack.Stack, "\n"), 77 }, 78 &protocol.Log_Content{ 79 Key: "stackID", 80 Value: strconv.FormatUint(id, 16), 81 }, 82 &protocol.Log_Content{ 83 Key: "language", 84 Value: meta.SpyName, 85 }, 86 &protocol.Log_Content{ 87 Key: "dataType", 88 Value: "CallStack", 89 }, 90 &protocol.Log_Content{ 91 Key: "durationNs", 92 Value: strconv.FormatInt(endTime-startTime, 10), 93 }, 94 95 &protocol.Log_Content{ 96 Key: "profileID", 97 Value: profileID, 98 }, 99 &protocol.Log_Content{ 100 Key: "labels", 101 Value: string(b), 102 }, 103 ) 104 for i, v := range vals { 105 var res []*protocol.Log_Content 106 if i != len(vals)-1 { 107 res = make([]*protocol.Log_Content, len(content)) 108 copy(res, content) 109 } else { 110 res = content 111 } 112 res = append(res, 113 &protocol.Log_Content{ 114 Key: "units", 115 Value: units[i], 116 }, 117 &protocol.Log_Content{ 118 Key: "type", 119 Value: profile.DetectProfileType(types[i]).Kind, 120 }, 121 &protocol.Log_Content{ 122 Key: "valueTypes", 123 Value: types[i], 124 }, 125 &protocol.Log_Content{ 126 Key: "aggTypes", 127 Value: aggs[i], 128 }, 129 &protocol.Log_Content{ 130 Key: "val", 131 Value: strconv.FormatFloat(float64(v), 'f', 2, 64), 132 }, 133 ) 134 log := &protocol.Log{ 135 Contents: res, 136 } 137 protocol.SetLogTimeWithNano(log, uint32(startTime/1e9), uint32(startTime%1e9)) 138 r.logs = append(r.logs, log) 139 } 140 } 141 } 142 143 func (r *RawProfile) extractProfileRaw() (io.Reader, *LabelsSnapshot, error) { 144 var reader io.Reader = bytes.NewReader(r.RawData) 145 var err error 146 labels := new(LabelsSnapshot) 147 if strings.Contains(r.FormDataContentType, "multipart/form-data") { 148 if reader, labels, err = loadJFRFromForm(reader, r.FormDataContentType); err != nil { 149 return nil, nil, err 150 } 151 } 152 return reader, labels, err 153 } 154 155 func loadJFRFromForm(r io.Reader, contentType string) (io.Reader, *LabelsSnapshot, error) { 156 boundary, err := form.ParseBoundary(contentType) 157 if err != nil { 158 return nil, nil, err 159 } 160 161 f, err := multipart.NewReader(r, boundary).ReadForm(32 << 20) 162 if err != nil { 163 return nil, nil, err 164 } 165 defer func() { 166 _ = f.RemoveAll() 167 }() 168 169 jfrField, err := form.ReadField(f, "jfr") 170 if err != nil { 171 return nil, nil, err 172 } 173 if jfrField == nil { 174 return nil, nil, fmt.Errorf("jfr field is required") 175 } 176 177 labelsField, err := form.ReadField(f, "labels") 178 if err != nil { 179 return nil, nil, err 180 } 181 var labels LabelsSnapshot 182 if len(labelsField) > 0 { 183 if err = proto.Unmarshal(labelsField, &labels); err != nil { 184 return nil, nil, err 185 } 186 } 187 188 return bytes.NewReader(jfrField), &labels, nil 189 }