pyroscope 0.3.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d24a58cc1e931711355e430131e01565c4c15c149ac96d0323826ad03101f22c
4
- data.tar.gz: 6f86a62f50e924859e404894bb47850a550b99fab146074f8c6cb4ddd6cf3089
3
+ metadata.gz: c54d01d273a289584bf1ecd0f69c7349e775f3e8a8b0a07a71a78095b4bbc78c
4
+ data.tar.gz: ef6dfa8b2da02bcfdd17d8a6d49cd86cfe7666615bcbb690bd04459c0300599d
5
5
  SHA512:
6
- metadata.gz: 8d65c0d1ef7eeb636caa2b945fb485144f81f934630bf3736ffccb65b966d9b3c66dea974d275f078c6d94613b52f0893f55ec145e066acd430cf55f1c8bfc9f
7
- data.tar.gz: 22e8e5a0b3a2a43bf11775539dabefa3e5d1d7d4bc6d7a3e40a4c3a80f117693b1a0b139f4e6d86c3db25da63b827b3f616531e385feabdd797cf4a5b3601c36
6
+ metadata.gz: c45a42bff9b659125547281a11a9623ef12469a91b61444a178d7895482a3889a6ed687f650cde9bb7bdfdaee163f5c1fa8a905e254116ad50ec6ece06f983d8
7
+ data.tar.gz: '0108e948e4d9bf98a729b5628fe6d243d849e7d7721463b8f5623629330d8ebbbaa1cc95dbddd5966ab0849409ca9858240414169d69182a7dbc92e583ebb6dd'
data/README.md CHANGED
@@ -1,57 +1,71 @@
1
- Pyroscope Ruby Integration --Beta--
2
- =====================================
1
+ # Pyroscope Ruby Gem
3
2
 
4
- **note**: This is a beta release. It requires local compilation, might be
5
- buggy and is frequently updated. For the initial implementation, find it [here](https://github.com/pyroscope-io/pyroscope-ruby). Please report any [issues](https://github.com/pyroscope-io/pyroscope-rs/issues).
3
+ **Pyroscope integration for Ruby**
6
4
 
7
- ## Installation
5
+ [![license](https://img.shields.io/badge/license-Apache2.0-blue.svg)](LICENSE)
6
+ ![tests](https://github.com/pyroscope-io/pyroscope-rs/workflows/Tests/badge.svg)
7
+ ![build](https://github.com/pyroscope-io/pyroscope-rs/workflows/Build/badge.svg)
8
+ [![Gem version](https://badge.fury.io/rb/pyroscope.svg)](https://badge.fury.io/rb/pyroscope)
8
9
 
9
- 1. You need the Rust toolchain to compile the library locally. To install
10
- Rust:
10
+ ---
11
11
 
12
- ```
13
- curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain stable -y
14
- export PATH=$PATH:/root/.cargo/bin
15
- ```
12
+ ### What is Pyroscope
13
+ [Pyroscope](https://github.com/pyroscope-io/pyroscope) is a tool that lets you continuously profile your applications to prevent and debug performance issues in your code. It consists of a low-overhead agent which sends data to the Pyroscope server which includes a custom-built storage engine. This allows for you to store and query any applications profiling data in an extremely efficient and cost effective way.
16
14
 
17
- 2. Building/Insalling from Rubygems
18
15
 
19
- ```
20
- gem install pyroscope_beta
21
- ```
16
+ ### Supported platforms
22
17
 
23
- 3. Building/Installing from source
18
+ | Linux | macOS | Windows | Docker |
19
+ |:-----:|:-----:|:-------:|:------:|
20
+ | ✅ | ✅ | | ✅ |
24
21
 
25
- Change directory to `pyroscope_ffi/ruby` and run
22
+ ### Profiling Ruby applications
26
23
 
24
+ Add the `pyroscope` gem to your Gemfile:
25
+
26
+ ```bash
27
+ bundle add pyroscope
27
28
  ```
28
- gem build pyroscope.gemspec
29
- gem install ./pyroscope.gemspec
30
- ```
31
29
 
32
- ## Configuration
30
+ ### Basic Configuration
31
+
32
+ Add the following code to your application. If you're using rails, put this into `config/initializers` directory. This code will initialize pyroscope profiler and start profiling:
33
33
 
34
- Configuration is similar to the old package except for `application_name`:
34
+ ```ruby
35
+ require 'pyroscope'
35
36
 
37
+ Pyroscope.configure do |config|
38
+ config.application_name = "my.ruby.app" # replace this with some name for your application
39
+ config.server_address = "http://my-pyroscope-server:4040" # replace this with the address of your pyroscope server
40
+ # config.auth_token = "{YOUR_API_KEY}" # optionally, if authentication is enabled, specify the API key
41
+ end
36
42
  ```
37
- require 'pyroscope_beta'
43
+
44
+ ### Tags
45
+
46
+ Pyroscope ruby integration provides a number of ways to tag profiling data. For example, you can provide tags when you're initializing the profiler:
47
+
48
+ ```ruby
49
+ require 'pyroscope'
38
50
 
39
51
  Pyroscope.configure do |config|
40
- config.application_name = "ruby.app"
41
- config.server_address = "http://localhost:4040"
42
- config.detect_subprocesses = true
52
+ config.application_name = "my.ruby.app"
53
+ config.server_address = "http://my-pyroscope-server:4040"
54
+
43
55
  config.tags = {
44
- :key => "value",
56
+ "hostname" => ENV["HOSTNAME"],
45
57
  }
46
58
  end
47
59
  ```
48
60
 
49
- ## Adding tags
61
+ or you can dynamically tag certain parts of your code:
50
62
 
51
- Tags passed to configure are global. To tag code locally, you can use:
52
-
53
- ```
54
- Pyroscope.tag_wrapper({"profile": "profile-1"}) do
55
- // Tagged profile
63
+ ```ruby
64
+ Pyroscope.tag_wrapper({ "controller": "slow_controller_i_want_to_profile" }) do
65
+ slow_code
56
66
  end
57
67
  ```
68
+
69
+ ### Example
70
+
71
+ Check out this [example ruby project in our repository](https://github.com/pyroscope-io/pyroscope/tree/main/examples/ruby) for examples of how you can use these features.
data/ext/rbspy/src/lib.rs CHANGED
@@ -1,17 +1,115 @@
1
- use ffikit::Signal;
2
- use pyroscope::backend::Tag;
3
- use pyroscope::PyroscopeAgent;
4
- use pyroscope_rbspy::{rbspy_backend, RbspyConfig};
5
1
  use std::collections::hash_map::DefaultHasher;
2
+ use std::env;
6
3
  use std::ffi::CStr;
7
4
  use std::hash::Hasher;
8
5
  use std::os::raw::c_char;
6
+ use std::str::FromStr;
7
+
8
+ use ffikit::Signal;
9
+ use pyroscope_rbspy::{rbspy_backend, RbspyConfig};
10
+
11
+ use pyroscope::{pyroscope::Compression, PyroscopeAgent};
12
+ use pyroscope::backend::{Report, StackFrame, Tag};
13
+ use pyroscope::pyroscope::ReportEncoding;
14
+
15
+ pub fn transform_report(report: Report) -> Report {
16
+ let cwd = env::current_dir().unwrap();
17
+ let cwd = cwd.to_str().unwrap_or("");
18
+
19
+ let data = report
20
+ .data
21
+ .iter()
22
+ .map(|(stacktrace, count)| {
23
+ let new_frames = stacktrace
24
+ .frames
25
+ .iter()
26
+ .map(|frame| {
27
+ let frame = frame.to_owned();
28
+ let mut s = frame.filename.unwrap();
29
+ match s.find(cwd) {
30
+ Some(i) => {
31
+ s = s[(i + cwd.len() + 1)..].to_string();
32
+ }
33
+ None => match s.find("/gems/") {
34
+ Some(i) => {
35
+ s = s[(i + 1)..].to_string();
36
+ }
37
+ None => match s.find("/ruby/") {
38
+ Some(i) => {
39
+ s = s[(i + 6)..].to_string();
40
+ match s.find("/") {
41
+ Some(i) => {
42
+ s = s[(i + 1)..].to_string();
43
+ }
44
+ None => {}
45
+ }
46
+ }
47
+ None => {}
48
+ },
49
+ },
50
+ }
51
+
52
+ // something
53
+ StackFrame::new(
54
+ frame.module,
55
+ frame.name,
56
+ Some(s.to_string()),
57
+ frame.relative_path,
58
+ frame.absolute_path,
59
+ frame.line,
60
+ )
61
+ })
62
+ .collect();
63
+
64
+ let mut mystack = stacktrace.to_owned();
65
+
66
+ mystack.frames = new_frames;
67
+
68
+ (mystack, count.to_owned())
69
+ })
70
+ .collect();
71
+
72
+ let new_report = Report::new(data).metadata(report.metadata.clone());
73
+
74
+ new_report
75
+ }
76
+
77
+ #[no_mangle]
78
+ pub extern "C" fn initialize_logging(logging_level: u32) -> bool {
79
+ // Force rustc to display the log messages in the console.
80
+ match logging_level {
81
+ 50 => {
82
+ std::env::set_var("RUST_LOG", "error");
83
+ }
84
+ 40 => {
85
+ std::env::set_var("RUST_LOG", "warn");
86
+ }
87
+ 30 => {
88
+ std::env::set_var("RUST_LOG", "info");
89
+ }
90
+ 20 => {
91
+ std::env::set_var("RUST_LOG", "debug");
92
+ }
93
+ 10 => {
94
+ std::env::set_var("RUST_LOG", "trace");
95
+ }
96
+ _ => {
97
+ std::env::set_var("RUST_LOG", "debug");
98
+ }
99
+ }
100
+
101
+ // Initialize the logger.
102
+ pretty_env_logger::init_timed();
103
+
104
+ true
105
+ }
9
106
 
10
107
  #[no_mangle]
11
108
  pub extern "C" fn initialize_agent(
12
109
  application_name: *const c_char, server_address: *const c_char, auth_token: *const c_char,
13
- sample_rate: u32, detect_subprocesses: bool, on_cpu: bool, report_pid: bool,
14
- report_thread_id: bool, tags: *const c_char,
110
+ sample_rate: u32, detect_subprocesses: bool, oncpu: bool, report_pid: bool,
111
+ report_thread_id: bool, tags: *const c_char, compression: *const c_char,
112
+ report_encoding: *const c_char
15
113
  ) -> bool {
16
114
  // Initialize FFIKit
17
115
  let recv = ffikit::initialize_ffi().unwrap();
@@ -36,13 +134,27 @@ pub extern "C" fn initialize_agent(
36
134
  .unwrap()
37
135
  .to_string();
38
136
 
137
+ let compression_string = unsafe { CStr::from_ptr(compression) }
138
+ .to_str()
139
+ .unwrap()
140
+ .to_string();
141
+
142
+ let report_encoding = unsafe { CStr::from_ptr(report_encoding) }
143
+ .to_str()
144
+ .unwrap()
145
+ .to_string();
146
+
147
+ let compression = Compression::from_str(&compression_string);
148
+ let report_encoding = ReportEncoding::from_str(&report_encoding)
149
+ .unwrap_or(ReportEncoding::FOLDED);
150
+
39
151
  let pid = std::process::id();
40
152
 
41
153
  let rbspy_config = RbspyConfig::new(pid.try_into().unwrap())
42
154
  .sample_rate(sample_rate)
43
155
  .lock_process(false)
44
- .with_subprocesses(detect_subprocesses)
45
- .on_cpu(on_cpu)
156
+ .detect_subprocesses(detect_subprocesses)
157
+ .oncpu(oncpu)
46
158
  .report_pid(report_pid)
47
159
  .report_thread_id(report_thread_id);
48
160
 
@@ -52,12 +164,18 @@ pub extern "C" fn initialize_agent(
52
164
 
53
165
  let mut agent_builder = PyroscopeAgent::builder(server_address, application_name)
54
166
  .backend(rbspy)
55
- .tags(tags);
167
+ .func(transform_report)
168
+ .tags(tags)
169
+ .report_encoding(report_encoding);
56
170
 
57
171
  if auth_token != "" {
58
172
  agent_builder = agent_builder.auth_token(auth_token);
59
173
  }
60
174
 
175
+ if let Ok(compression) = compression {
176
+ agent_builder = agent_builder.compression(compression);
177
+ }
178
+
61
179
  let agent = agent_builder.build().unwrap();
62
180
 
63
181
  let agent_running = agent.start().unwrap();
@@ -1,3 +1,3 @@
1
1
  module Pyroscope
2
- VERSION = '0.3.1'.freeze
2
+ VERSION = '0.5.0'.freeze
3
3
  end
data/lib/pyroscope.rb CHANGED
@@ -1,10 +1,14 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
1
4
  require 'ffi'
2
5
 
3
6
  module Pyroscope
4
7
  module Rust
5
8
  extend FFI::Library
6
9
  ffi_lib File.expand_path(File.dirname(__FILE__)) + "/rbspy/rbspy.#{RbConfig::CONFIG["DLEXT"]}"
7
- attach_function :initialize_agent, [:string, :string, :string, :int, :bool, :bool, :bool, :bool, :string], :bool
10
+ attach_function :initialize_logging, [:int], :bool
11
+ attach_function :initialize_agent, [:string, :string, :string, :int, :bool, :bool, :bool, :bool, :string, :string, :string], :bool
8
12
  attach_function :add_thread_tag, [:uint64, :string, :string], :bool
9
13
  attach_function :remove_thread_tag, [:uint64, :string, :string], :bool
10
14
  attach_function :add_global_tag, [:string, :string], :bool
@@ -18,19 +22,22 @@ module Pyroscope
18
22
  attach_function :thread_id, [], :uint64
19
23
  end
20
24
 
21
- Config = Struct.new(:application_name, :app_name, :server_address, :auth_token, :sample_rate, :detect_subprocesses, :on_cpu, :report_pid, :report_thread_id, :log_level, :tags) do
25
+ Config = Struct.new(:application_name, :app_name, :server_address, :auth_token, :log_level, :sample_rate, :detect_subprocesses, :oncpu, :report_pid, :report_thread_id, :tags, :compression, :report_encoding) do
22
26
  def initialize(*)
27
+ super
28
+ # defaults:
23
29
  self.application_name = ''
24
30
  self.server_address = 'http://localhost:4040'
25
31
  self.auth_token = ''
26
32
  self.sample_rate = 100
27
33
  self.detect_subprocesses = false
28
- self.on_cpu = true
34
+ self.oncpu = true
29
35
  self.report_pid = false
30
36
  self.report_thread_id = false
31
- self.log_level = 'info'
37
+ self.log_level = 'error'
32
38
  self.tags = {}
33
- super
39
+ self.compression = 'gzip'
40
+ self.report_encoding = 'pprof'
34
41
  end
35
42
  end
36
43
 
@@ -41,16 +48,40 @@ module Pyroscope
41
48
  # Pass config to the block
42
49
  yield @config
43
50
 
51
+ # Determine Logging level (kinda like an enum).
52
+ case @config.log_level
53
+ when 'trace'
54
+ @log_level = 10
55
+ when 'debug'
56
+ @log_level = 20
57
+ when 'info'
58
+ @log_level = 30
59
+ when 'warn'
60
+ @log_level = 40
61
+ when 'error'
62
+ @log_level = 50
63
+ else
64
+ @log_level = 50
65
+ end
66
+
67
+ # Initialize Logging
68
+ Rust.initialize_logging(@log_level)
69
+
70
+
71
+ # initialize Pyroscope Agent
44
72
  Rust.initialize_agent(
73
+ # these are defaults in case user-provided values are nil:
45
74
  @config.app_name || @config.application_name || "",
46
75
  @config.server_address || "",
47
76
  @config.auth_token || "",
48
77
  @config.sample_rate || 100,
49
78
  @config.detect_subprocesses || false,
50
- @config.on_cpu || false,
79
+ @config.oncpu || false,
51
80
  @config.report_pid || false,
52
81
  @config.report_thread_id || false,
53
- tags_to_string(@config.tags || {})
82
+ tags_to_string(@config.tags || {}),
83
+ @config.compression || "",
84
+ @config.report_encoding || "pprof"
54
85
  )
55
86
  end
56
87
 
@@ -96,7 +127,7 @@ module Pyroscope
96
127
  end
97
128
  end
98
129
 
99
- def drop
130
+ def shutdown
100
131
  Rust.drop_agent
101
132
  end
102
133
  end
data/pyroscope.gemspec CHANGED
@@ -1,4 +1,11 @@
1
- require_relative "lib/pyroscope/version"
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ begin
5
+ require File.expand_path(File.join(File.dirname(__FILE__), "lib/pyroscope/version"))
6
+ rescue LoadError
7
+ puts "WARNING: Could not load Pyroscope::VERSION"
8
+ end
2
9
 
3
10
  Gem::Specification.new do |s|
4
11
  s.name = 'pyroscope'
@@ -9,6 +16,13 @@ Gem::Specification.new do |s|
9
16
  s.email = ['contact@pyroscope.io']
10
17
  s.homepage = 'https://pyroscope.io'
11
18
  s.license = 'Apache-2.0'
19
+ s.metadata = {
20
+ "homepage_uri" => "https://pyroscope.io",
21
+ "bug_tracker_uri" => "https://github.com/pyroscope-io/pyroscope-rs/issues",
22
+ "documentation_uri" => "https://pyroscope.io/docs/ruby/",
23
+ "changelog_uri" => "https://github.com/pyroscope-io/pyroscope-rs/tree/main/pyroscope_ffi/ruby/CHANGELOG.md",
24
+ "source_code_uri" => "https://github.com/pyroscope-io/pyroscope-rs/tree/main/pyroscope_ffi/ruby",
25
+ }
12
26
 
13
27
  # Specify which files should be added to the gem when it is released.
14
28
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
@@ -21,7 +35,7 @@ Gem::Specification.new do |s|
21
35
 
22
36
  s.platform = Gem::Platform::RUBY
23
37
 
24
- s.required_ruby_version = ">= 2.5.9"
38
+ s.required_ruby_version = ">= 1.9.3"
25
39
 
26
40
  s.extensions = ['ext/rbspy/extconf.rb', 'ext/thread_id/extconf.rb']
27
41
 
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "pyroscope"
4
+ require "pyroscope/version"
5
+
6
+ puts Pyroscope::VERSION
7
+ puts RUBY_VERSION
8
+
9
+ Pyroscope.configure do |config|
10
+ config.application_name = "#{ENV["PYROSCOPE_RUN_ID"]}"
11
+ config.server_address = "https://ingest.pyroscope.cloud"
12
+ config.auth_token = ENV["PYROSCOPE_API_TOKEN"]
13
+ config.detect_subprocesses = ENV["PYROSCOPE_DETECT_SUBPROCESSES"] == "1"
14
+ config.oncpu = ENV["PYROSCOPE_ONCPU"] == "1"
15
+ config.log_level = "trace"
16
+ config.report_pid = true
17
+ config.report_thread_id = true
18
+ config.tags = {
19
+ :region => "us-east",
20
+ :detect_subprocesses => ENV["PYROSCOPE_DETECT_SUBPROCESSES"],
21
+ :oncpu => ENV["PYROSCOPE_ONCPU"],
22
+ :version => ENV["RUBY_VERSION"],
23
+ :arch => ENV["PYROSCOPE_ARCH"]
24
+ }
25
+ end
26
+
27
+ def work(n)
28
+ i = 0
29
+ while i < n
30
+ i += 1
31
+ end
32
+ end
33
+
34
+ def fast_function
35
+ Pyroscope.tag_wrapper({"function": "fast"}) do
36
+ work(2001002000)
37
+ end
38
+ end
39
+
40
+ def slow_function
41
+ work(8001008000)
42
+ end
43
+
44
+ child_pid = fork do
45
+ puts "This is the child process"
46
+ Pyroscope.tag_wrapper({"fork": "forked"}) do
47
+ slow_function()
48
+ end
49
+ end
50
+
51
+ puts "This is the master process."
52
+
53
+ Pyroscope.tag_wrapper({"fork": "master"}) do
54
+ fast_function()
55
+ end
56
+
57
+ puts "The PID of the child process is #{child_pid}"
58
+
59
+ Pyroscope.shutdown()
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pyroscope
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pyroscope Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-07-19 00:00:00.000000000 Z
11
+ date: 2022-09-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi
@@ -89,10 +89,16 @@ files:
89
89
  - lib/pyroscope/version.rb
90
90
  - pyroscope.gemspec
91
91
  - scripts/docker.sh
92
+ - scripts/tests/test.rb
92
93
  homepage: https://pyroscope.io
93
94
  licenses:
94
95
  - Apache-2.0
95
- metadata: {}
96
+ metadata:
97
+ homepage_uri: https://pyroscope.io
98
+ bug_tracker_uri: https://github.com/pyroscope-io/pyroscope-rs/issues
99
+ documentation_uri: https://pyroscope.io/docs/ruby/
100
+ changelog_uri: https://github.com/pyroscope-io/pyroscope-rs/tree/main/pyroscope_ffi/ruby/CHANGELOG.md
101
+ source_code_uri: https://github.com/pyroscope-io/pyroscope-rs/tree/main/pyroscope_ffi/ruby
96
102
  post_install_message:
97
103
  rdoc_options: []
98
104
  require_paths:
@@ -101,7 +107,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
101
107
  requirements:
102
108
  - - ">="
103
109
  - !ruby/object:Gem::Version
104
- version: 2.5.9
110
+ version: 1.9.3
105
111
  required_rubygems_version: !ruby/object:Gem::Requirement
106
112
  requirements:
107
113
  - - ">="