working commit
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
target
|
||||
.env
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2023-present Dylibso, Inc.
|
||||
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.
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
package observe
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
trace "go.opentelemetry.io/proto/otlp/trace/v1"
|
||||
)
|
||||
|
||||
// The primary interface that every Adapter needs to follow
|
||||
// Start() and Stop() can just call the implementations on AdapterBase
|
||||
// or provide some custom logic. HandleTraceEvent is called after
|
||||
// an invocation of a wasm module is done and all events are collected.
|
||||
type Adapter interface {
|
||||
Start(context.Context)
|
||||
Stop(wait bool)
|
||||
HandleTraceEvent(e TraceEvent)
|
||||
}
|
||||
|
||||
// The payload that contains all the Events
|
||||
// from a single wasm module invocation
|
||||
type TraceEvent struct {
|
||||
Events []Event
|
||||
TelemetryId TelemetryId
|
||||
AdapterMeta interface{}
|
||||
}
|
||||
|
||||
// Shared implementation for all Adapters
|
||||
type AdapterBase struct {
|
||||
TraceEvents chan TraceEvent
|
||||
|
||||
stop chan bool
|
||||
eventBucket *EventBucket
|
||||
flusher Flusher
|
||||
}
|
||||
|
||||
func (a *AdapterBase) NewTraceCtx(ctx context.Context, r wazero.Runtime, wasm []byte, opts *Options) (*TraceCtx, error) {
|
||||
if opts == nil {
|
||||
opts = NewDefaultOptions()
|
||||
}
|
||||
return newTraceCtx(ctx, a.TraceEvents, r, wasm, opts)
|
||||
}
|
||||
|
||||
func NewAdapterBase(batchSize int, flushPeriod time.Duration) AdapterBase {
|
||||
bucket := NewEventBucket(batchSize, flushPeriod)
|
||||
return AdapterBase{
|
||||
TraceEvents: make(chan TraceEvent, 100),
|
||||
eventBucket: bucket,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *AdapterBase) SetFlusher(f Flusher) {
|
||||
b.flusher = f
|
||||
}
|
||||
|
||||
func (b *AdapterBase) HandleTraceEvent(te TraceEvent) {
|
||||
b.eventBucket.addEvent(te, b.flusher)
|
||||
}
|
||||
|
||||
func (b *AdapterBase) Start(ctx context.Context, a Adapter) {
|
||||
b.stop = make(chan bool)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.Println("context cancelled")
|
||||
return
|
||||
case event := <-b.TraceEvents:
|
||||
a.HandleTraceEvent(event)
|
||||
case <-b.stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Stops the adapter and waits for all flushes to complete.
|
||||
// Set wait parameter to false if you don't want to wait
|
||||
func (b *AdapterBase) Stop(wait bool) {
|
||||
b.stop <- true
|
||||
if wait {
|
||||
b.eventBucket.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
// MakeOtelCallSpans recursively constructs call spans in open telemetry format
|
||||
func (b *AdapterBase) MakeOtelCallSpans(event CallEvent, parentId []byte, traceId string) []*trace.Span {
|
||||
name := event.FunctionName()
|
||||
span := NewOtelSpan(traceId, parentId, name, event.Time, event.Time.Add(event.Duration))
|
||||
span.Attributes = append(span.Attributes, NewOtelKeyValueString("function-name", fmt.Sprintf("function-call-%s", name)))
|
||||
|
||||
spans := []*trace.Span{span}
|
||||
for _, ev := range event.Within() {
|
||||
if call, ok := ev.(CallEvent); ok {
|
||||
spans = append(spans, b.MakeOtelCallSpans(call, span.SpanId, traceId)...)
|
||||
}
|
||||
if alloc, ok := ev.(MemoryGrowEvent); ok {
|
||||
kv := NewOtelKeyValueInt64("allocation", int64(alloc.MemoryGrowAmount()))
|
||||
i, existing := GetOtelAttrFromSpan("allocation", span)
|
||||
if existing != nil {
|
||||
span.Attributes[i] = AddOtelKeyValueInt64(kv, existing)
|
||||
} else {
|
||||
span.Attributes = append(span.Attributes, kv)
|
||||
}
|
||||
}
|
||||
if tags, ok := ev.(SpanTagsEvent); ok {
|
||||
for _, tag := range tags.Tags {
|
||||
parts := strings.Split(tag, ":")
|
||||
if len(parts) != 2 {
|
||||
log.Printf("Invalid tag: %s\n", tag)
|
||||
continue
|
||||
}
|
||||
|
||||
kv := NewOtelKeyValueString(parts[0], parts[1])
|
||||
span.Attributes = append(span.Attributes, kv)
|
||||
}
|
||||
}
|
||||
}
|
||||
return spans
|
||||
}
|
||||
|
||||
// Definition of how to filter our Spans to reduce noise
|
||||
type SpanFilter struct {
|
||||
MinDuration time.Duration
|
||||
}
|
||||
|
||||
// Specify options to change what or how the adapter receives ObserveEvents
|
||||
type Options struct {
|
||||
SpanFilter *SpanFilter
|
||||
ChannelBufferSize int
|
||||
}
|
||||
|
||||
// Create a default configuration
|
||||
func NewDefaultOptions() *Options {
|
||||
return &Options{
|
||||
ChannelBufferSize: 1024,
|
||||
SpanFilter: &SpanFilter{
|
||||
MinDuration: time.Microsecond * 20,
|
||||
},
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
package observe
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Flusher interface {
|
||||
Flush(events []TraceEvent) error
|
||||
}
|
||||
|
||||
// EventBucket is a bucket for outgoing TraceEvents.
|
||||
// It only schedules flushes when the bucket goes from empty to 1 item.
|
||||
// At most the latency to flush the bucket will be flushPeriod.
|
||||
// It will also flush the TraceEvents in batches according to batch size
|
||||
type EventBucket struct {
|
||||
mu sync.Mutex
|
||||
wg sync.WaitGroup
|
||||
bucket []TraceEvent
|
||||
flushPeriod time.Duration
|
||||
batchSize int
|
||||
}
|
||||
|
||||
// NewEventBucket creates an EventBucket
|
||||
func NewEventBucket(batchSize int, flushPeriod time.Duration) *EventBucket {
|
||||
return &EventBucket{
|
||||
flushPeriod: flushPeriod,
|
||||
batchSize: batchSize,
|
||||
}
|
||||
}
|
||||
|
||||
// addEvent adds a TraceEvent and schedules to flush to Flusher if needed
|
||||
func (b *EventBucket) addEvent(e TraceEvent, f Flusher) {
|
||||
b.mu.Lock()
|
||||
wasEmpty := len(b.bucket) == 0
|
||||
b.bucket = append(b.bucket, e)
|
||||
b.mu.Unlock()
|
||||
// if this is the first event in the bucket,
|
||||
// we schedule a flush
|
||||
if wasEmpty {
|
||||
b.scheduleFlush(f)
|
||||
}
|
||||
}
|
||||
|
||||
// Wait will block until all pending flushes are done
|
||||
func (b *EventBucket) Wait() {
|
||||
b.wg.Wait()
|
||||
}
|
||||
|
||||
// scheduleFlush schedules a goroutine to flush
|
||||
// the bucket at some time in the future depending on flushPeriod.
|
||||
// Events will continue to build up until the flush comes due
|
||||
func (b *EventBucket) scheduleFlush(f Flusher) {
|
||||
// we start this routine and immediately wait, we are effectively
|
||||
// scheduling the flush to run flushPeriod sections later. In the meantime,
|
||||
// events may still be coming into the eventBucket
|
||||
go func() {
|
||||
// register this flush with the wait group
|
||||
defer b.wg.Done()
|
||||
b.wg.Add(1)
|
||||
|
||||
// wait for flushPeriod
|
||||
time.Sleep(b.flushPeriod)
|
||||
|
||||
// move the events out of the EventBucket to a slice
|
||||
// and add 1 to the waitgroup
|
||||
b.mu.Lock()
|
||||
bucket := b.bucket
|
||||
b.bucket = nil
|
||||
b.mu.Unlock()
|
||||
|
||||
// flush the bucket in chunks of batchSize
|
||||
for i := 0; i < len(bucket); i += b.batchSize {
|
||||
j := i + b.batchSize
|
||||
if j > len(bucket) {
|
||||
j = len(bucket)
|
||||
}
|
||||
// TODO retry logic?
|
||||
err := f.Flush(bucket[i:j])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
package observe
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/ianlancetaylor/demangle"
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
)
|
||||
|
||||
type RawEventKind int
|
||||
|
||||
const (
|
||||
RawEnter RawEventKind = iota
|
||||
RawExit
|
||||
RawMemoryGrow
|
||||
RawMetric
|
||||
RawSpanTags
|
||||
RawLog
|
||||
RawUnknownEvent
|
||||
)
|
||||
|
||||
type EventKind int
|
||||
|
||||
const (
|
||||
Call EventKind = iota
|
||||
MemoryGrow
|
||||
Custom
|
||||
Metric
|
||||
SpanTags
|
||||
Log
|
||||
)
|
||||
|
||||
type MetricFormat uint
|
||||
|
||||
const (
|
||||
StatsdFormat MetricFormat = 1
|
||||
)
|
||||
|
||||
// Represents the raw event in our Observe form.
|
||||
// Events are transformed into vendor specific formats
|
||||
// in the Adapters.
|
||||
type RawEvent struct {
|
||||
Kind RawEventKind
|
||||
Stack []experimental.InternalFunction
|
||||
FunctionIndex uint32
|
||||
FunctionName string
|
||||
MemoryGrowAmount uint32
|
||||
Time time.Time
|
||||
Duration time.Duration
|
||||
}
|
||||
|
||||
type Event interface {
|
||||
RawEvents() []RawEvent
|
||||
}
|
||||
|
||||
type CallEvent struct {
|
||||
Raw []RawEvent
|
||||
Time time.Time
|
||||
Duration time.Duration
|
||||
within []Event
|
||||
}
|
||||
|
||||
func (e *CallEvent) Stop(at time.Time) {
|
||||
e.Duration = at.Sub(e.Time)
|
||||
}
|
||||
|
||||
func (e CallEvent) RawEvents() []RawEvent {
|
||||
return e.Raw
|
||||
}
|
||||
|
||||
func (e CallEvent) Within() []Event {
|
||||
return e.within
|
||||
}
|
||||
|
||||
type CustomEvent struct {
|
||||
Time time.Time
|
||||
Name string
|
||||
Metadata map[string]interface{}
|
||||
}
|
||||
|
||||
func NewCustomEvent(name string) CustomEvent {
|
||||
return CustomEvent{
|
||||
Time: time.Now(),
|
||||
Name: name,
|
||||
Metadata: map[string]interface{}{},
|
||||
}
|
||||
}
|
||||
|
||||
func (e CustomEvent) RawEvents() []RawEvent {
|
||||
return []RawEvent{}
|
||||
}
|
||||
|
||||
type MetricEvent struct {
|
||||
Time time.Time
|
||||
Format MetricFormat
|
||||
Message string
|
||||
}
|
||||
|
||||
type SpanTagsEvent struct {
|
||||
Raw RawEvent
|
||||
Time time.Time
|
||||
Tags []string
|
||||
}
|
||||
|
||||
type MemoryGrowEvent struct {
|
||||
Raw RawEvent
|
||||
Time time.Time
|
||||
}
|
||||
|
||||
type LogLevel uint
|
||||
|
||||
const (
|
||||
Error LogLevel = 1
|
||||
Warn = 2
|
||||
Info = 3
|
||||
Debug = 4
|
||||
)
|
||||
|
||||
type LogEvent struct {
|
||||
Time time.Time
|
||||
Message string
|
||||
Level LogLevel
|
||||
}
|
||||
|
||||
func (e MemoryGrowEvent) RawEvents() []RawEvent {
|
||||
return []RawEvent{e.Raw}
|
||||
}
|
||||
|
||||
func (e MemoryGrowEvent) FunctionName() string {
|
||||
s, err := demangle.ToString(e.Raw.FunctionName)
|
||||
if err != nil {
|
||||
return e.Raw.FunctionName
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (e MemoryGrowEvent) FunctionIndex() uint32 {
|
||||
return e.Raw.FunctionIndex
|
||||
}
|
||||
|
||||
func (e CallEvent) FunctionName() string {
|
||||
s, err := demangle.ToString(e.Raw[0].FunctionName)
|
||||
if err != nil {
|
||||
return e.Raw[0].FunctionName
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (e CallEvent) FunctionIndex() uint32 {
|
||||
return e.Raw[0].FunctionIndex
|
||||
}
|
||||
|
||||
func (e MemoryGrowEvent) MemoryGrowAmount() uint32 {
|
||||
return e.Raw.MemoryGrowAmount
|
||||
}
|
||||
|
||||
func (e MetricEvent) RawEvents() []RawEvent {
|
||||
return []RawEvent{}
|
||||
}
|
||||
|
||||
func (e SpanTagsEvent) RawEvents() []RawEvent {
|
||||
return []RawEvent{e.Raw}
|
||||
}
|
||||
|
||||
func (e LogEvent) RawEvents() []RawEvent {
|
||||
return []RawEvent{}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package observe
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
)
|
||||
|
||||
// Implements the NewListener() method to satisfy the FunctionListener interface
|
||||
func (t *TraceCtx) NewListener(def api.FunctionDefinition) experimental.FunctionListener {
|
||||
if def.GoFunction() == nil {
|
||||
return nil
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// Implements the NewFunctionListener() method to satisfy the FunctionListener interface
|
||||
func (t *TraceCtx) NewFunctionListener(_ api.FunctionDefinition) experimental.FunctionListener {
|
||||
return t
|
||||
}
|
||||
|
||||
// Implements the Before() method to satisfy the FunctionListener interface.
|
||||
// This takes events from the wazero runtime and sends them to the `raw` channel on the TraceCtx.
|
||||
func (t *TraceCtx) Before(ctx context.Context, _ api.Module, def api.FunctionDefinition, inputs []uint64, stack experimental.StackIterator) {
|
||||
var event RawEvent
|
||||
name := def.Name()
|
||||
|
||||
switch name {
|
||||
case "enter":
|
||||
fallthrough
|
||||
case "instrument_enter":
|
||||
event.Kind = RawEnter
|
||||
event.FunctionIndex = uint32(inputs[0])
|
||||
event.FunctionName = t.names[event.FunctionIndex]
|
||||
case "exit":
|
||||
fallthrough
|
||||
case "instrument_exit":
|
||||
event.Kind = RawExit
|
||||
event.FunctionIndex = uint32(inputs[0])
|
||||
event.FunctionName = t.names[event.FunctionIndex]
|
||||
case "memory-grow":
|
||||
fallthrough
|
||||
case "instrument_memory_grow":
|
||||
event.Kind = RawMemoryGrow
|
||||
event.MemoryGrowAmount = uint32(inputs[0])
|
||||
|
||||
// manual events
|
||||
case "span-enter":
|
||||
fallthrough
|
||||
case "span_enter":
|
||||
event.Kind = RawEnter
|
||||
case "span-exit":
|
||||
fallthrough
|
||||
case "span_exit":
|
||||
event.Kind = RawExit
|
||||
case "span-tags":
|
||||
fallthrough
|
||||
case "span_tags":
|
||||
event.Kind = RawSpanTags
|
||||
case "metric":
|
||||
return
|
||||
case "log":
|
||||
return
|
||||
default:
|
||||
event.Kind = RawUnknownEvent
|
||||
}
|
||||
for stack.Next() {
|
||||
f := stack.Function()
|
||||
event.Stack = append(event.Stack, f)
|
||||
}
|
||||
t.raw <- event
|
||||
}
|
||||
|
||||
// Null implementation of the After() method to satisfy the FunctionListener interface.
|
||||
func (t *TraceCtx) After(context.Context, api.Module, api.FunctionDefinition, []uint64) {}
|
||||
|
||||
// Null implementation of the Abort() method to satisfy the FunctionListener interface.
|
||||
func (t *TraceCtx) Abort(context.Context, api.Module, api.FunctionDefinition, error) {}
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
package observe
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"time"
|
||||
|
||||
common "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
resource "go.opentelemetry.io/proto/otlp/resource/v1"
|
||||
trace "go.opentelemetry.io/proto/otlp/trace/v1"
|
||||
)
|
||||
|
||||
type OtelTrace struct {
|
||||
TraceId string
|
||||
TracesData *trace.TracesData
|
||||
}
|
||||
|
||||
func NewOtelTrace(traceId string, serviceName string, spans []*trace.Span) *OtelTrace {
|
||||
return &OtelTrace{
|
||||
TraceId: traceId,
|
||||
TracesData: &trace.TracesData{
|
||||
ResourceSpans: []*trace.ResourceSpans{
|
||||
{
|
||||
Resource: &resource.Resource{
|
||||
Attributes: []*common.KeyValue{
|
||||
NewOtelKeyValueString("service.name", serviceName),
|
||||
},
|
||||
},
|
||||
ScopeSpans: []*trace.ScopeSpans{
|
||||
{
|
||||
Spans: spans,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (t *OtelTrace) SetMetadata(te *TraceEvent, meta map[string]string) {
|
||||
for _, rs := range t.TracesData.ResourceSpans {
|
||||
for _, ss := range rs.ScopeSpans {
|
||||
for _, span := range ss.Spans {
|
||||
for key, value := range meta {
|
||||
span.Attributes = append(span.Attributes, NewOtelKeyValueString(key, value))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewOtelSpan(traceId string, parentId []byte, name string, start, end time.Time) *trace.Span {
|
||||
if parentId == nil {
|
||||
parentId = []byte{}
|
||||
}
|
||||
|
||||
traceIdB, err := hex.DecodeString(traceId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
spanId := NewSpanId().Msb()
|
||||
spanIdB := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(spanIdB, spanId)
|
||||
|
||||
return &trace.Span{
|
||||
TraceId: traceIdB,
|
||||
SpanId: spanIdB,
|
||||
ParentSpanId: parentId,
|
||||
Name: name,
|
||||
Kind: 1,
|
||||
StartTimeUnixNano: uint64(start.UnixNano()),
|
||||
EndTimeUnixNano: uint64(end.UnixNano()),
|
||||
// uses empty defaults for remaining fields...
|
||||
}
|
||||
}
|
||||
|
||||
func NewOtelKeyValueString(key string, value string) *common.KeyValue {
|
||||
strVal := &common.AnyValue_StringValue{
|
||||
StringValue: value,
|
||||
}
|
||||
return &common.KeyValue{
|
||||
Key: key,
|
||||
Value: &common.AnyValue{
|
||||
Value: strVal,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewOtelKeyValueInt64(key string, value int64) *common.KeyValue {
|
||||
intVal := &common.AnyValue_IntValue{
|
||||
IntValue: value,
|
||||
}
|
||||
return &common.KeyValue{
|
||||
Key: key,
|
||||
Value: &common.AnyValue{
|
||||
Value: intVal,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func GetOtelAttrFromSpan(attr string, span *trace.Span) (int, *common.KeyValue) {
|
||||
for i, attr := range span.Attributes {
|
||||
if attr.Key == "allocation" {
|
||||
return i, attr
|
||||
}
|
||||
}
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
func AddOtelKeyValueInt64(kvs ...*common.KeyValue) *common.KeyValue {
|
||||
if len(kvs) > 0 {
|
||||
retKv := &common.KeyValue{
|
||||
Key: kvs[0].Key,
|
||||
Value: kvs[0].Value,
|
||||
}
|
||||
for i := 1; i < len(kvs); i++ {
|
||||
v, ok := retKv.Value.Value.(*common.AnyValue_IntValue)
|
||||
if ok {
|
||||
curr, ok := kvs[i].Value.Value.(*common.AnyValue_IntValue)
|
||||
if ok {
|
||||
intVal := &common.AnyValue_IntValue{
|
||||
IntValue: v.IntValue + curr.IntValue,
|
||||
}
|
||||
retKv.Value.Value = intVal
|
||||
}
|
||||
}
|
||||
}
|
||||
return retKv
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
package observe
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
// This is a shared type for a span or trace id.
|
||||
// It's represented by 2 uint64s and can be transformed
|
||||
// to different string or int representations where needed.
|
||||
type TelemetryId struct {
|
||||
lsb uint64
|
||||
msb uint64
|
||||
}
|
||||
|
||||
var rng rand.Source
|
||||
|
||||
func init() {
|
||||
rng = rand.NewSource(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// Create a new trace id
|
||||
func NewTraceId() TelemetryId {
|
||||
return TelemetryId{
|
||||
msb: uint64(rng.Int63()),
|
||||
lsb: uint64(rng.Int63()),
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new span id
|
||||
func NewSpanId() TelemetryId {
|
||||
return TelemetryId{
|
||||
msb: uint64(rng.Int63()),
|
||||
lsb: uint64(rng.Int63()),
|
||||
}
|
||||
}
|
||||
|
||||
func (id TelemetryId) Msb() uint64 {
|
||||
return id.msb
|
||||
}
|
||||
|
||||
func (id TelemetryId) Lsb() uint64 {
|
||||
return id.lsb
|
||||
}
|
||||
|
||||
// Encode this id into an 8 byte hex (16 chars)
|
||||
// Just uses the least significant of the 16 bytes
|
||||
func (t TelemetryId) ToHex8() string {
|
||||
return fmt.Sprintf("%016x", t.lsb)
|
||||
}
|
||||
|
||||
// Encode this id into a 16 byte hex (32 chars)
|
||||
// Uses both 16 byte uint64 values
|
||||
func (t TelemetryId) ToHex16() string {
|
||||
return fmt.Sprintf("%016x%016x", t.msb, t.lsb)
|
||||
}
|
||||
|
||||
// Some adapters may need a raw representation
|
||||
func (t TelemetryId) ToUint64() uint64 {
|
||||
return t.lsb
|
||||
}
|
||||
|
||||
func (t *TelemetryId) FromBytes(id []byte) error {
|
||||
if len(id) != 16 {
|
||||
return errors.New("TraceID must be 16 bytes")
|
||||
}
|
||||
|
||||
t.msb = binary.BigEndian.Uint64(id)
|
||||
t.lsb = binary.BigEndian.Uint64(id[8:])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TelemetryId) FromString(id string) error {
|
||||
b, err := hex.DecodeString(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return t.FromBytes(b)
|
||||
}
|
||||
+380
@@ -0,0 +1,380 @@
|
||||
package observe
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
)
|
||||
|
||||
// TraceCtx holds the context for a trace, or wasm module invocation.
|
||||
// It collects holds a channel to the Adapter and from the wazero Listener
|
||||
// It will collect events throughout the invocation of the function. Calling
|
||||
// Finish() will then submit those events to the Adapter to be processed and sent
|
||||
type TraceCtx struct {
|
||||
adapter chan TraceEvent
|
||||
raw chan RawEvent
|
||||
events []Event
|
||||
stack []CallEvent
|
||||
Options *Options
|
||||
names map[uint32]string
|
||||
telemetryId TelemetryId
|
||||
adapterMeta interface{}
|
||||
}
|
||||
|
||||
// Creates a new TraceCtx. Used internally by the Adapter. The user should create the trace context from the Adapter.
|
||||
func newTraceCtx(ctx context.Context, eventsChan chan TraceEvent, r wazero.Runtime, data []byte, opts *Options) (*TraceCtx, error) {
|
||||
names, err := parseNames(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if opts.ChannelBufferSize == 0 {
|
||||
opts.ChannelBufferSize = 64 // set a reasonable minimum here so unset option doesn't block execution on an unbuffered channel
|
||||
}
|
||||
|
||||
traceCtx := &TraceCtx{
|
||||
adapter: eventsChan,
|
||||
raw: make(chan RawEvent, opts.ChannelBufferSize),
|
||||
names: names,
|
||||
telemetryId: NewTraceId(),
|
||||
Options: opts,
|
||||
}
|
||||
|
||||
err = traceCtx.init(ctx, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return traceCtx, nil
|
||||
}
|
||||
|
||||
func (t *TraceCtx) SetTraceId(id string) error {
|
||||
return t.telemetryId.FromString(id)
|
||||
}
|
||||
|
||||
func (t *TraceCtx) Metadata(metadata interface{}) {
|
||||
t.adapterMeta = metadata
|
||||
}
|
||||
|
||||
// Finish() will stop the trace and send the
|
||||
// TraceEvent payload to the adapter
|
||||
func (t *TraceCtx) Finish() {
|
||||
traceEvent := TraceEvent{
|
||||
Events: t.events,
|
||||
TelemetryId: t.telemetryId,
|
||||
AdapterMeta: t.adapterMeta,
|
||||
}
|
||||
t.adapter <- traceEvent
|
||||
// clear the trace context
|
||||
t.events = nil
|
||||
t.telemetryId = NewTraceId()
|
||||
}
|
||||
|
||||
// Attaches the wazero FunctionListener to the context
|
||||
func (t *TraceCtx) withListener(ctx context.Context) context.Context {
|
||||
return experimental.WithFunctionListenerFactory(ctx, t)
|
||||
}
|
||||
|
||||
// Initializes the TraceCtx. This connects up the channels with events from the FunctionListener.
|
||||
// Should only be called once.
|
||||
func (t *TraceCtx) init(ctx context.Context, r wazero.Runtime) error {
|
||||
ctx = t.withListener(ctx)
|
||||
|
||||
if r.Module("dylibso_observe") != nil || r.Module("dylibso:observe/instrument") != nil ||
|
||||
r.Module("dylibso:observe/api") != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
enterFunc := func(ctx context.Context, m api.Module, i uint32) {
|
||||
start := time.Now()
|
||||
ev := <-t.raw
|
||||
|
||||
t.enter(ev, start)
|
||||
}
|
||||
|
||||
spanEnterFunc := func(ctx context.Context, m api.Module, ptr uint32, len uint32) {
|
||||
start := time.Now()
|
||||
ev := <-t.raw
|
||||
|
||||
functionName, ok := m.Memory().Read(ptr, len)
|
||||
if !ok {
|
||||
log.Printf("span_enter: failed to read memory at offset %v with length %v\n", ptr, len)
|
||||
}
|
||||
|
||||
ev.FunctionName = string(functionName)
|
||||
|
||||
t.enter(ev, start)
|
||||
}
|
||||
|
||||
oldSpanEnterFunc := func(ctx context.Context, m api.Module, ptr uint64, len uint32) {
|
||||
spanEnterFunc(ctx, m, uint32(ptr), len)
|
||||
}
|
||||
|
||||
exitFunc := func(ctx context.Context, i uint32) {
|
||||
end := time.Now()
|
||||
ev := <-t.raw
|
||||
|
||||
t.exit(ev, end)
|
||||
}
|
||||
|
||||
spanExitFunc := func(ctx context.Context, m api.Module) {
|
||||
end := time.Now()
|
||||
ev := <-t.raw
|
||||
|
||||
t.exit(ev, end)
|
||||
}
|
||||
|
||||
memoryGrowFunc := func(ctx context.Context, amt uint32) {
|
||||
ev := <-t.raw
|
||||
if ev.Kind != RawMemoryGrow {
|
||||
log.Println("Expected event", MemoryGrow, "but got", ev.Kind)
|
||||
return
|
||||
}
|
||||
|
||||
if len(t.stack) > 0 {
|
||||
f := t.stack[len(t.stack)-1]
|
||||
ev.FunctionIndex = f.FunctionIndex()
|
||||
ev.FunctionName = f.FunctionName()
|
||||
}
|
||||
|
||||
event := MemoryGrowEvent{
|
||||
Raw: ev,
|
||||
Time: time.Now(),
|
||||
}
|
||||
|
||||
fn, ok := t.popFunction()
|
||||
if !ok {
|
||||
t.events = append(t.events, event)
|
||||
return
|
||||
}
|
||||
fn.within = append(fn.within, event)
|
||||
t.pushFunction(fn)
|
||||
}
|
||||
|
||||
metricFunc := func(ctx context.Context, m api.Module, f uint32, ptr uint32, l uint32) {
|
||||
format := MetricFormat(f)
|
||||
buffer, ok := m.Memory().Read(ptr, l)
|
||||
if !ok {
|
||||
log.Printf("metric: failed to read memory at offset %v with length %v\n", ptr, l)
|
||||
}
|
||||
|
||||
event := MetricEvent{
|
||||
Time: time.Now(),
|
||||
Format: format,
|
||||
Message: string(buffer),
|
||||
}
|
||||
|
||||
fn, ok := t.popFunction()
|
||||
if !ok {
|
||||
t.events = append(t.events, event)
|
||||
return
|
||||
}
|
||||
fn.within = append(fn.within, event)
|
||||
t.pushFunction(fn)
|
||||
}
|
||||
|
||||
oldMetricFunc := func(ctx context.Context, m api.Module, f uint32, ptr uint64, len uint32) {
|
||||
metricFunc(ctx, m, f, uint32(ptr), len)
|
||||
}
|
||||
|
||||
spanTagsFunc := func(ctx context.Context, m api.Module, ptr uint32, len uint32) {
|
||||
buffer, ok := m.Memory().Read(ptr, len)
|
||||
if !ok {
|
||||
log.Printf("span-tags: failed to read memory at offset %v with length %v\n", ptr, len)
|
||||
}
|
||||
|
||||
ev := <-t.raw
|
||||
if ev.Kind != RawSpanTags {
|
||||
log.Println("Expected event", SpanTags, "but got", ev.Kind)
|
||||
return
|
||||
}
|
||||
|
||||
event := SpanTagsEvent{
|
||||
Time: time.Now(),
|
||||
Raw: ev,
|
||||
Tags: strings.Split(string(buffer), ","),
|
||||
}
|
||||
|
||||
fn, ok := t.popFunction()
|
||||
if !ok {
|
||||
t.events = append(t.events, event)
|
||||
return
|
||||
}
|
||||
fn.within = append(fn.within, event)
|
||||
t.pushFunction(fn)
|
||||
}
|
||||
|
||||
oldSpanTagsFunc := func(ctx context.Context, m api.Module, ptr uint64, len uint32) {
|
||||
spanTagsFunc(ctx, m, uint32(ptr), len)
|
||||
}
|
||||
|
||||
logFunc := func(ctx context.Context, m api.Module, l uint32, ptr uint32, len uint32) {
|
||||
if l < uint32(Error) || l > uint32(Debug) {
|
||||
log.Printf("log: invalid log level %v\n", l)
|
||||
}
|
||||
|
||||
level := LogLevel(l)
|
||||
|
||||
buffer, ok := m.Memory().Read(ptr, len)
|
||||
if !ok {
|
||||
log.Printf("log: failed to read memory at offset %v with length %v\n", ptr, len)
|
||||
}
|
||||
|
||||
event := LogEvent{
|
||||
Time: time.Now(),
|
||||
Level: level,
|
||||
Message: string(buffer),
|
||||
}
|
||||
|
||||
fn, ok := t.popFunction()
|
||||
if !ok {
|
||||
t.events = append(t.events, event)
|
||||
return
|
||||
}
|
||||
fn.within = append(fn.within, event)
|
||||
t.pushFunction(fn)
|
||||
}
|
||||
|
||||
oldLogFunc := func(ctx context.Context, m api.Module, l uint32, ptr uint64, len uint32) {
|
||||
logFunc(ctx, m, l, uint32(ptr), len)
|
||||
}
|
||||
|
||||
// instrument api
|
||||
{
|
||||
instrument := r.NewHostModuleBuilder("dylibso:observe/instrument")
|
||||
instrFunctions := instrument.NewFunctionBuilder()
|
||||
instrFunctions.WithFunc(enterFunc).Export("enter")
|
||||
instrFunctions.WithFunc(exitFunc).Export("exit")
|
||||
instrFunctions.WithFunc(memoryGrowFunc).Export("memory-grow")
|
||||
_, err := instrument.Instantiate(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// manual api
|
||||
{
|
||||
api := r.NewHostModuleBuilder("dylibso:observe/api")
|
||||
apiFunctions := api.NewFunctionBuilder()
|
||||
apiFunctions.WithFunc(spanEnterFunc).Export("span-enter")
|
||||
apiFunctions.WithFunc(spanExitFunc).Export("span-exit")
|
||||
apiFunctions.WithFunc(spanTagsFunc).Export("span-tags")
|
||||
apiFunctions.WithFunc(metricFunc).Export("metric")
|
||||
apiFunctions.WithFunc(logFunc).Export("log")
|
||||
_, err := api.Instantiate(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
//old api (combined instrument and manual api)
|
||||
{
|
||||
observe := r.NewHostModuleBuilder("dylibso_observe")
|
||||
observeFunctions := observe.NewFunctionBuilder()
|
||||
observeFunctions.WithFunc(enterFunc).Export("instrument_enter")
|
||||
observeFunctions.WithFunc(exitFunc).Export("instrument_exit")
|
||||
observeFunctions.WithFunc(memoryGrowFunc).Export("instrument_memory_grow")
|
||||
observeFunctions.WithFunc(oldSpanEnterFunc).Export("span_enter")
|
||||
observeFunctions.WithFunc(spanExitFunc).Export("span_exit")
|
||||
observeFunctions.WithFunc(oldSpanTagsFunc).Export("span_tags")
|
||||
observeFunctions.WithFunc(oldMetricFunc).Export("metric")
|
||||
observeFunctions.WithFunc(oldLogFunc).Export("log")
|
||||
_, err := observe.Instantiate(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TraceCtx) enter(ev RawEvent, start time.Time) {
|
||||
if ev.Kind != RawEnter {
|
||||
log.Println("Expected event", RawEnter, "but got", ev.Kind)
|
||||
}
|
||||
t.pushFunction(CallEvent{Raw: []RawEvent{ev}, Time: start})
|
||||
}
|
||||
|
||||
func (t *TraceCtx) exit(ev RawEvent, end time.Time) {
|
||||
|
||||
if ev.Kind != RawExit {
|
||||
log.Println("Expected event", RawExit, "but got", ev.Kind)
|
||||
return
|
||||
}
|
||||
fn, ok := t.peekFunction()
|
||||
if !ok {
|
||||
log.Println("Expected values on started function stack, but none were found")
|
||||
return
|
||||
}
|
||||
if ev.FunctionIndex != fn.FunctionIndex() {
|
||||
log.Println("Expected call to", ev.FunctionIndex, "but found call to", fn.FunctionIndex())
|
||||
return
|
||||
}
|
||||
|
||||
fn, _ = t.popFunction()
|
||||
fn.Stop(end)
|
||||
fn.Raw = append(fn.Raw, ev)
|
||||
|
||||
// if there is no function left to pop, we are exiting the root function of the trace
|
||||
f, ok := t.peekFunction()
|
||||
if !ok {
|
||||
t.events = append(t.events, fn)
|
||||
return
|
||||
}
|
||||
|
||||
// if the function duration is less than minimum duration, disregard
|
||||
funcDuration := fn.Duration.Microseconds()
|
||||
minSpanDuration := t.Options.SpanFilter.MinDuration.Microseconds()
|
||||
if funcDuration < minSpanDuration {
|
||||
// check for memory allocations and attribute them to the parent span
|
||||
f, ok = t.popFunction()
|
||||
if ok {
|
||||
for _, ev := range fn.within {
|
||||
switch e := ev.(type) {
|
||||
case MemoryGrowEvent:
|
||||
f.within = append(f.within, e)
|
||||
}
|
||||
}
|
||||
t.pushFunction(f)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// the function is within another function
|
||||
f, ok = t.popFunction()
|
||||
if ok {
|
||||
f.within = append(f.within, fn)
|
||||
t.pushFunction(f)
|
||||
}
|
||||
}
|
||||
|
||||
// Pushes a function onto the stack
|
||||
func (t *TraceCtx) pushFunction(ev CallEvent) {
|
||||
t.stack = append(t.stack, ev)
|
||||
}
|
||||
|
||||
// Pops a function off the stack
|
||||
func (t *TraceCtx) popFunction() (CallEvent, bool) {
|
||||
if len(t.stack) == 0 {
|
||||
return CallEvent{}, false
|
||||
}
|
||||
|
||||
event := t.stack[len(t.stack)-1]
|
||||
t.stack = t.stack[:len(t.stack)-1]
|
||||
|
||||
return event, true
|
||||
}
|
||||
|
||||
// Peek at the function on top of the stack without modifying
|
||||
func (t *TraceCtx) peekFunction() (CallEvent, bool) {
|
||||
if len(t.stack) == 0 {
|
||||
return CallEvent{}, false
|
||||
}
|
||||
|
||||
return t.stack[len(t.stack)-1], true
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package observe
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
|
||||
"github.com/tetratelabs/wabin/binary"
|
||||
"github.com/tetratelabs/wabin/wasm"
|
||||
)
|
||||
|
||||
// Parse the names of the functions out of the
|
||||
// names custom section in the wasm binary.
|
||||
func parseNames(data []byte) (map[uint32]string, error) {
|
||||
features := wasm.CoreFeaturesV2
|
||||
m, err := binary.DecodeModule(data, features)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if m.NameSection == nil {
|
||||
return nil, errors.New("Name section not found")
|
||||
}
|
||||
|
||||
names := make(map[uint32]string, len(m.NameSection.FunctionNames))
|
||||
|
||||
for _, v := range m.NameSection.FunctionNames {
|
||||
names[v.Index] = v.Name
|
||||
}
|
||||
|
||||
warnOnDylibsoObserve := true
|
||||
for _, item := range m.ImportSection {
|
||||
if item.Module == "dylibso_observe" {
|
||||
if warnOnDylibsoObserve {
|
||||
warnOnDylibsoObserve = false
|
||||
log.Println("Module uses deprecated namespace \"dylibso_observe\"!\n" +
|
||||
"Please consider reinstrumenting with newer wasm-instr or Observe API!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return names, nil
|
||||
}
|
||||
Reference in New Issue
Block a user