Skip to content

Commit

Permalink
Adds .Net support via dotnetspy (#117)
Browse files Browse the repository at this point in the history
  • Loading branch information
kolesnikovae committed May 17, 2021
1 parent 812ca3f commit 084e461
Show file tree
Hide file tree
Showing 13 changed files with 284 additions and 2 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ ifeq ("$(shell go env GOARCH || true)", "arm64")
GODEBUG=asyncpreemptoff=1
endif

ALL_SPIES = "ebpfspy,rbspy,pyspy,debugspy"
ALL_SPIES = "ebpfspy,rbspy,pyspy,dotnetspy,debugspy"
ifeq ("$(shell go env GOOS || true)", "linux")
ENABLED_SPIES ?= "ebpfspy,rbspy,pyspy,phpspy"
ENABLED_SPIES ?= "ebpfspy,rbspy,pyspy,phpspy,dotnetspy"
else
ENABLED_SPIES ?= "rbspy,pyspy"
endif
Expand Down
13 changes: 13 additions & 0 deletions examples/dotnet/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM mcr.microsoft.com/dotnet/sdk:5.0

WORKDIR /dotnet

COPY --from=pyroscope/pyroscope:dev /usr/bin/pyroscope /usr/bin/pyroscope
ADD example .

RUN dotnet publish -o . -r linux-x64

ENV PYROSCOPE_APPLICATION_NAME=simple.dotnet.app
ENV PYROSCOPE_SERVER_ADDRESS=http://pyroscope:4040/
ENV PYROSCOPE_LOG_LEVEL=debug
CMD ["pyroscope", "exec", "dotnet", "/dotnet/example"]
13 changes: 13 additions & 0 deletions examples/dotnet/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
version: "3.9"
services:
pyroscope:
image: "pyroscope/pyroscope:latest"
ports:
- "4040:4040"
command:
- "server"
app:
environment:
ASPNETCORE_URLS: http://*:5000
build: .
8 changes: 8 additions & 0 deletions examples/dotnet/example/Example.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<AssemblyName>example</AssemblyName>
<OutputType>Exe</OutputType>
<PackageId>example</PackageId>
</PropertyGroup>
</Project>
15 changes: 15 additions & 0 deletions examples/dotnet/example/Pages/Index.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@page
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Page Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" media="screen" href="main.css" />
<script src="main.js"></script>
</head>
<body>
<h1>Hello</h1>
</body>
</html>
38 changes: 38 additions & 0 deletions examples/dotnet/example/src/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace PracticalAspNetCore
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
}

public void Configure(IApplicationBuilder app)
{
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
}

public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
webBuilder.UseStartup<Startup>()
);
}
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ require (
github.com/pelletier/go-toml v1.8.1 // indirect
github.com/peterbourgon/ff v1.7.0
github.com/peterbourgon/ff/v3 v3.0.0
github.com/pyroscope-io/dotnetdiag v0.0.0-20210517153448-072eb40e6376 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/sirupsen/logrus v1.7.0
github.com/twmb/murmur3 v1.1.5
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/pyroscope-io/dotnetdiag v0.0.0-20210517153448-072eb40e6376 h1:OgeNdESx0hbW7GYexZK7URZmVcl1XsonNwbLLn0MQd8=
github.com/pyroscope-io/dotnetdiag v0.0.0-20210517153448-072eb40e6376/go.mod h1:mUudCmW+j2ewS55/FHq0lHj0Xy2AHWc1UhaaRdfSve8=
github.com/pyroscope-io/revive v1.0.6-0.20210330033039-4a71146f9dc1 h1:0v9lBNgdmVtpyyk9PP/DfpJlOHkXriu5YgNlrhQw5YE=
github.com/pyroscope-io/revive v1.0.6-0.20210330033039-4a71146f9dc1/go.mod h1:tSw34BaGZ0iF+oVKDOjq1/LuxGifgW7shaJ6+dBYFXg=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
Expand Down
44 changes: 44 additions & 0 deletions pkg/agent/dotnetspy/dotnetspy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// +build dotnetspy

package dotnetspy

import (
"sync"
)

type DotnetSpy struct {
session *session
m sync.Mutex
reset bool
}

func Start(pid int) (*DotnetSpy, error) {
s := newSession(pid)
err := s.Start()
if err != nil {
return nil, err
}
return &DotnetSpy{session: s}, nil
}

func (s *DotnetSpy) Stop() error {
return s.session.Stop()
}

func (s *DotnetSpy) Reset() {
s.m.Lock()
defer s.m.Unlock()
s.reset = true
}

func (s *DotnetSpy) Snapshot(cb func([]byte, uint64, error)) {
s.m.Lock()
defer s.m.Unlock()
if !s.reset {
return
}
s.reset = false
s.session.Flush(func(name []byte, v uint64) {
cb(name, v, nil)
})
}
3 changes: 3 additions & 0 deletions pkg/agent/dotnetspy/placeholder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// +build !dotnetspy

package dotnetspy
142 changes: 142 additions & 0 deletions pkg/agent/dotnetspy/session.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// +build dotnetspy

package dotnetspy

import (
"io"
"strings"
"sync"
"time"

"github.com/pyroscope-io/dotnetdiag"
"github.com/pyroscope-io/dotnetdiag/nettrace"
"github.com/pyroscope-io/dotnetdiag/nettrace/profiler"
)

type session struct {
client *dotnetdiag.Client
config dotnetdiag.CollectTracingConfig
session *dotnetdiag.Session

ch chan line
m sync.Mutex
stop bool
}

type line struct {
name []byte
val int
}

func newSession(pid int) *session {
return &session{
client: dotnetdiag.NewClient(dotnetdiag.DefaultServerAddress(pid)),
config: dotnetdiag.CollectTracingConfig{
CircularBufferSizeMB: 10,
Providers: []dotnetdiag.ProviderConfig{
{
Keywords: 0x0000F00000000000,
LogLevel: 4,
ProviderName: "Microsoft-DotNETCore-SampleProfiler",
},
},
},
}
}

func (s *session) Start() error {
ns, err := s.client.CollectTracing(s.config)
if err != nil {
return err
}

s.session = ns
stream := nettrace.NewStream(ns)
trace, err := stream.Open()
if err != nil {
_ = ns.Close()
return err
}

p := profiler.NewSampleProfiler(trace)
stream.EventHandler = p.EventHandler
stream.MetadataHandler = p.MetadataHandler
stream.StackBlockHandler = p.StackBlockHandler
stream.SequencePointBlockHandler = p.SequencePointBlockHandler

s.ch = make(chan line)
r := newRenderer(func(name []byte, val int) {
s.ch <- line{
name: name,
val: val,
}
})

go func() {
defer close(s.ch)
for {
switch err = stream.Next(); err {
default:
case nil:
continue
case io.EOF:
p.Walk(r.visitor)
r.flush()
}
return
}
}()

return nil
}

func (s *session) Flush(cb func([]byte, uint64)) error {
s.session.Close()
for v := range s.ch {
cb(v.name, uint64(v.val))
}
s.m.Lock()
defer s.m.Unlock()
if s.stop {
return nil
}
return s.Start()
}

func (s *session) Stop() error {
s.m.Lock()
defer s.m.Unlock()
s.session.Close()
s.stop = true
return nil
}

type renderer struct {
callBack func(name []byte, val int)
names []string
val time.Duration
prev int
}

func newRenderer(cb func(name []byte, val int)) *renderer {
return &renderer{callBack: cb}
}

func (r *renderer) visitor(frame profiler.FrameInfo) {
if frame.Level > r.prev || (frame.Level == 0 && r.prev == 0) {
r.names = append(r.names, frame.Name)
} else {
r.flush()
if frame.Level == 0 {
r.names = []string{frame.Name}
} else {
r.names = append(r.names[:frame.Level], frame.Name)
}
}
r.val = frame.SampledTime
r.prev = frame.Level
}

func (r *renderer) flush() {
r.callBack([]byte(strings.Join(r.names, ";")), int(r.val.Milliseconds()))
}
1 change: 1 addition & 0 deletions pkg/agent/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
// That's why we do a blank import here and then packages themselves register with the rest of the code.

_ "github.com/pyroscope-io/pyroscope/pkg/agent/debugspy"
_ "github.com/pyroscope-io/pyroscope/pkg/agent/dotnetspy"
_ "github.com/pyroscope-io/pyroscope/pkg/agent/ebpfspy"
"github.com/pyroscope-io/pyroscope/pkg/agent/gospy"
_ "github.com/pyroscope-io/pyroscope/pkg/agent/gospy"
Expand Down
2 changes: 2 additions & 0 deletions pkg/analytics/analytics.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ type metrics struct {
SpyGospy int `json:"spy_gospy"`
SpyEbpfspy int `json:"spy_ebpfspy"`
SpyPhpspy int `json:"spy_phpspy"`
SpyDotnetspy int `json:"spy_dotnetspy"`
AppsCount int `json:"apps_count"`
}

Expand Down Expand Up @@ -142,6 +143,7 @@ func (s *Service) sendReport() {
SpyGospy: controllerStats["ingest:gospy"],
SpyEbpfspy: controllerStats["ingest:ebpfspy"],
SpyPhpspy: controllerStats["ingest:phpspy"],
SpyDotnetspy: controllerStats["ingest:dotnetspy"],
AppsCount: s.c.AppsCount(),
}

Expand Down

0 comments on commit 084e461

Please sign in to comment.