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

Reformat offsets_result.json for brevity and better human visibility #45

Merged
merged 7 commits into from
Apr 28, 2023
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ OpenTelemetry Go Automatic Instrumentation adheres to [Semantic Versioning](http
### Added

- Add [gin-gonic/gin](https://github.com/gin-gonic/gin) instrumentation. ([#100](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/100))

### Changed

- Use verion spans in `offsets_results.json` instead of storing each version. ([#45](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/45))
- Change `OTEL_TARGET_EXE` environment variable to `OTEL_GO_AUTO_TARGET_EXE`.
([#97](https://github.com/open-telemetry/opentelemetry-go-instrumentation/issues/97))

Expand Down
77 changes: 51 additions & 26 deletions offsets-tracker/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@ import (
"log"
"os"

"github.com/hashicorp/go-version"

"go.opentelemetry.io/auto/offsets-tracker/binary"
"go.opentelemetry.io/auto/offsets-tracker/schema"
"go.opentelemetry.io/auto/offsets-tracker/versions"
)

// Cache holds already seen offsets.
Expand Down Expand Up @@ -57,34 +60,56 @@ func NewCache(prevOffsetFile string) *Cache {
}
}

// IsAllInCache returns all DataMemberOffset for a module with modName and
// version and offsets describe by dm.
func (c *Cache) IsAllInCache(modName string, version string, dm []*binary.DataMember) ([]*binary.DataMemberOffset, bool) {
for _, item := range c.data.Data {
if item.Name == modName {
offsetsFound := 0
var results []*binary.DataMemberOffset
for _, offsets := range item.DataMembers {
for _, targetDm := range dm {
if offsets.Struct == targetDm.StructName && offsets.Field == targetDm.Field {
for _, ver := range offsets.Offsets {
if ver.Version == version {
results = append(results, &binary.DataMemberOffset{
DataMember: targetDm,
Offset: ver.Offset,
})
offsetsFound++
}
}
}
}
}
if offsetsFound == len(dm) {
return results, true
}
// IsAllInCache checks whether the passed datamembers exist in the cache for a
// given version.
func (c *Cache) IsAllInCache(version string, dataMembers []*binary.DataMember) ([]*binary.DataMemberOffset, bool) {
var results []*binary.DataMemberOffset
for _, dm := range dataMembers {
// first, look for the field and check that the target version is in
// chache.
strct, ok := c.data.Data[dm.StructName]
if !ok {
return nil, false
}
field, ok := strct[dm.Field]
if !ok {
return nil, false
}
if !versions.Between(version, field.Versions.Oldest, field.Versions.Newest) {
return nil, false
}

off, ok := searchOffset(field, version)
if !ok {
return nil, false
}
results = append(results, &binary.DataMemberOffset{
DataMember: dm,
Offset: off,
})
}
return results, true
}

// searchOffset searches an offset from the newest field whose version
// is lower than or equal to the target version.
func searchOffset(field schema.TrackedField, targetVersion string) (uint64, bool) {
target := versions.MustParse(targetVersion)

// Search from the newest version
for o := len(field.Offsets) - 1; o >= 0; o-- {
od := &field.Offsets[o]
fieldVersion, err := version.NewVersion(od.Since)
if err != nil {
// Malformed version: return not found
return 0, false
}
if target.Compare(fieldVersion) >= 0 {
// if target version is larger or equal than lib version:
// we certainly know that it is the most recent tracked offset
return od.Offset, true
}
}

return nil, false
return 0, false
}
31 changes: 19 additions & 12 deletions offsets-tracker/schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,31 @@ package schema

// TrackedOffsets are all the tracked offsets.
type TrackedOffsets struct {
Data []TrackedLibrary `json:"data"`
// Data key: struct name, which includes the library name in external
// libraries.
Data map[string]TrackedStruct `json:"data"`
}

// TrackedLibrary are all the offsets for a tracked package.
type TrackedLibrary struct {
Name string `json:"name"`
DataMembers []TrackedDataMember `json:"data_members"`
}
// TrackedStruct maps fields names to the tracked fields offsets.
type TrackedStruct map[string]TrackedField

// TrackedDataMember are the Offsets for struct from a package.
type TrackedDataMember struct {
Struct string `json:"struct"`
Field string `json:"field_name"`
// TrackedField are the field offsets for a tracked struct.
type TrackedField struct {
// Versions range that are tracked for this given field.
Versions VersionInfo `json:"versions"`
// Offsets are the sorted version offsets for the field. These need to be
// sorted in descending order.
Offsets []VersionedOffset `json:"offsets"`
}

// VersionInfo is the span of supported versions.
type VersionInfo struct {
Oldest string `json:"oldest"`
Newest string `json:"newest"`
}

// VersionedOffset if a offset for a version of a module.
type VersionedOffset struct {
Offset uint64 `json:"offset"`
Version string `json:"version"`
Offset uint64 `json:"offset"`
Since string `json:"since"`
}
2 changes: 1 addition & 1 deletion offsets-tracker/target/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func (t *Data) FindOffsets(dm []*binary.DataMember) (*Result, error) {
}
for _, v := range vers {
if t.cache != nil {
cachedResults, found := t.cache.IsAllInCache(t.name, v, dm)
cachedResults, found := t.cache.IsAllInCache(v, dm)
if found {
fmt.Printf("%s: Found all requested offsets in cache for version %s\n", t.name, v)
result.ResultsByVersion = append(result.ResultsByVersion, &VersionedResult{
Expand Down
38 changes: 38 additions & 0 deletions offsets-tracker/versions/tools.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package versions

import "github.com/hashicorp/go-version"

// Between returns if the target contains a version between the lowerBound and
// higherBound.
func Between(target, lowerBound, higherBound string) bool {
v := MustParse(target)

return v.GreaterThanOrEqual(MustParse(lowerBound)) &&
v.LessThanOrEqual(MustParse(higherBound))
}

// MustParse must be used with versions that are embedded in the code or generated by us,
// so we are sure that the format is correct. For versions that are provided by a user
// we should handle properly the error.
func MustParse(v string) *version.Version {
p, err := version.NewVersion(v)
if err != nil {
// if we reach this point, there must be a bug so we panic
panic(err)
}
return p
}
103 changes: 69 additions & 34 deletions offsets-tracker/writer/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,34 +23,21 @@ import (
"sort"
"strings"

"github.com/hashicorp/go-version"

"go.opentelemetry.io/auto/offsets-tracker/schema"
"go.opentelemetry.io/auto/offsets-tracker/target"
"go.opentelemetry.io/auto/offsets-tracker/versions"
)

// WriteResults writes results to fileName.
func WriteResults(fileName string, results ...*target.Result) error {
var offsets schema.TrackedOffsets
for _, r := range results {
offsets.Data = append(offsets.Data, convertResult(r))
offsets := schema.TrackedOffsets{
Data: map[string]schema.TrackedStruct{},
}

// sort data for consistent output
for i := 0; i < len(offsets.Data); i++ {
trackedLibrary := offsets.Data[i]
sort.Slice(trackedLibrary.DataMembers, func(i, j int) bool {
dataMemberi := trackedLibrary.DataMembers[i]
dataMemberj := trackedLibrary.DataMembers[j]
if dataMemberi.Struct != dataMemberj.Struct {
return dataMemberi.Struct < dataMemberj.Struct
}
return dataMemberi.Field < dataMemberj.Field
})
for _, r := range results {
convertResult(r, &offsets)
}
sort.Slice(offsets.Data, func(i, j int) bool {
trackedLibraryi := offsets.Data[i]
trackedLibraryj := offsets.Data[j]
return trackedLibraryi.Name < trackedLibraryj.Name
})

jsonData, err := json.Marshal(&offsets)
if err != nil {
Expand All @@ -66,30 +53,78 @@ func WriteResults(fileName string, results ...*target.Result) error {
return os.WriteFile(fileName, prettyJSON.Bytes(), fs.ModePerm)
}

func convertResult(r *target.Result) schema.TrackedLibrary {
tl := schema.TrackedLibrary{
Name: r.ModuleName,
}

func convertResult(r *target.Result, offsets *schema.TrackedOffsets) {
offsetsMap := make(map[string][]schema.VersionedOffset)
for _, vr := range r.ResultsByVersion {
for _, od := range vr.OffsetData.DataMembers {
key := fmt.Sprintf("%s,%s", od.StructName, od.Field)
offsetsMap[key] = append(offsetsMap[key], schema.VersionedOffset{
Offset: od.Offset,
Version: vr.Version,
Offset: od.Offset,
Since: vr.Version,
})
}
}

for key, offsets := range offsetsMap {
parts := strings.Split(key, ",")
tl.DataMembers = append(tl.DataMembers, schema.TrackedDataMember{
Struct: parts[0],
Field: parts[1],
Offsets: offsets,
// normalize offsets: just annotate the offsets from the version
// that changed them
fieldVersionsMap := map[string]hiLoSemVers{}
for key, offs := range offsetsMap {
if len(offs) == 0 {
continue
}
// the algorithm below assumes offsets versions are sorted from older to newer
sort.Slice(offs, func(i, j int) bool {
return versions.MustParse(offs[i].Since).
LessThanOrEqual(versions.MustParse(offs[j].Since))
})

hilo := hiLoSemVers{}
var om []schema.VersionedOffset
var last schema.VersionedOffset
for n, off := range offs {
hilo.updateModuleVersion(off.Since)
// only append versions that changed the field value from its predecessor
if n == 0 || off.Offset != last.Offset {
om = append(om, off)
}
last = off
}
offsetsMap[key] = om
fieldVersionsMap[key] = hilo
}

// Append offsets as fields to the existing file map map
for key, offs := range offsetsMap {
parts := strings.Split(key, ",")
strFields, ok := offsets.Data[parts[0]]
if !ok {
strFields = schema.TrackedStruct{}
offsets.Data[parts[0]] = strFields
}
hl := fieldVersionsMap[key]
strFields[parts[1]] = schema.TrackedField{
Offsets: offs,
Versions: schema.VersionInfo{
Oldest: hl.lo.String(),
Newest: hl.hi.String(),
},
}
}
}

// hiLoSemVers track highest and lowest version.
type hiLoSemVers struct {
hi *version.Version
lo *version.Version
}

return tl
func (hl *hiLoSemVers) updateModuleVersion(vr string) {
ver := versions.MustParse(vr)

if hl.lo == nil || ver.LessThan(hl.lo) {
hl.lo = ver
}
if hl.hi == nil || ver.GreaterThan(hl.hi) {
hl.hi = ver
}
}
31 changes: 19 additions & 12 deletions pkg/inject/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,32 @@ package inject

// TrackedOffsets are the offsets for all instrumented packages.
type TrackedOffsets struct {
Data []TrackedLibrary `json:"data"`
// Data key: struct name, which includes the library name in external
// libraries.
Data map[string]TrackedStruct `json:"data"`
}

// TrackedLibrary are the offsets for an instrumented package.
type TrackedLibrary struct {
Name string `json:"name"`
DataMembers []TrackedDataMember `json:"data_members"`
}
// TrackedStruct maps fields names to the tracked fields offsets.
type TrackedStruct map[string]TrackedField

// TrackedDataMember are the offsets for a data type from a package.
type TrackedDataMember struct {
Struct string `json:"struct"`
Field string `json:"field_name"`
// TrackedField are the field offsets for a tracked struct.
type TrackedField struct {
// Versions range that are tracked for this given field
Versions VersionInfo `json:"versions"`
// Offsets are the sorted version offsets for the field. These need to be
// sorted in descending order.
Offsets []VersionedOffset `json:"offsets"`
}

// VersionInfo is the span of supported versions.
type VersionInfo struct {
Oldest string `json:"oldest"`
Newest string `json:"newest"`
}

// VersionedOffset is the offset for a particular version of a data type from a
// package.
type VersionedOffset struct {
Offset uint64 `json:"offset"`
Version string `json:"version"`
Offset uint64 `json:"offset"`
Since string `json:"since"`
}
Loading