test-prof 0.4.9 → 0.5.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +63 -20
  3. data/README.md +10 -57
  4. data/assets/tagprof.demo.html +447 -0
  5. data/assets/tagprof.template.html +447 -0
  6. data/lib/minitest/event_prof_formatter.rb +18 -16
  7. data/lib/test_prof.rb +2 -1
  8. data/lib/test_prof/any_fixture.rb +80 -4
  9. data/lib/test_prof/any_fixture/dsl.rb +18 -0
  10. data/lib/test_prof/event_prof.rb +10 -108
  11. data/lib/test_prof/event_prof/custom_events.rb +30 -0
  12. data/lib/test_prof/event_prof/custom_events/factory_create.rb +1 -1
  13. data/lib/test_prof/event_prof/custom_events/sidekiq_inline.rb +1 -1
  14. data/lib/test_prof/event_prof/custom_events/sidekiq_jobs.rb +1 -1
  15. data/lib/test_prof/event_prof/minitest.rb +6 -0
  16. data/lib/test_prof/event_prof/profiler.rb +129 -0
  17. data/lib/test_prof/event_prof/rspec.rb +20 -11
  18. data/lib/test_prof/factory_all_stub.rb +32 -0
  19. data/lib/test_prof/factory_all_stub/factory_bot_patch.rb +13 -0
  20. data/lib/test_prof/factory_doctor/rspec.rb +3 -2
  21. data/lib/test_prof/factory_prof/printers/flamegraph.rb +9 -13
  22. data/lib/test_prof/recipes/active_record_shared_connection.rb +55 -0
  23. data/lib/test_prof/recipes/logging.rb +37 -0
  24. data/lib/test_prof/recipes/rspec/any_fixture.rb +4 -1
  25. data/lib/test_prof/recipes/rspec/factory_all_stub.rb +10 -0
  26. data/lib/test_prof/rspec_dissect/rspec.rb +4 -2
  27. data/lib/test_prof/rspec_stamp/rspec.rb +3 -2
  28. data/lib/test_prof/tag_prof.rb +4 -0
  29. data/lib/test_prof/tag_prof/printers/html.rb +24 -0
  30. data/lib/test_prof/tag_prof/printers/simple.rb +82 -0
  31. data/lib/test_prof/tag_prof/result.rb +38 -0
  32. data/lib/test_prof/tag_prof/rspec.rb +43 -40
  33. data/lib/test_prof/utils/html_builder.rb +21 -0
  34. data/lib/test_prof/version.rb +1 -1
  35. metadata +20 -24
  36. data/assets/logo.svg +0 -1
  37. data/assets/testprof.png +0 -0
  38. data/guides/.rubocop.yml +0 -1
  39. data/guides/any_fixture.md +0 -114
  40. data/guides/before_all.md +0 -98
  41. data/guides/event_prof.md +0 -177
  42. data/guides/factory_default.md +0 -111
  43. data/guides/factory_doctor.md +0 -119
  44. data/guides/factory_prof.md +0 -86
  45. data/guides/let_it_be.md +0 -97
  46. data/guides/rspec_dissect.md +0 -60
  47. data/guides/rspec_stamp.md +0 -52
  48. data/guides/rubocop.md +0 -48
  49. data/guides/ruby_prof.md +0 -63
  50. data/guides/stack_prof.md +0 -47
  51. data/guides/tag_prof.md +0 -51
  52. data/guides/tests_sampling.md +0 -24
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 34e45def589431c2dd53ec256e009877c9a611ad1fa1cd7799cf6ec75c15745c
4
- data.tar.gz: 2230613ed9c7e5bf9733a16824f7e61daa77ed524b846de4195168e89a8b5d0e
3
+ metadata.gz: 8953166be33111ca0da788c4a867d5bda07d8df917cd486dda045d5464ce44ab
4
+ data.tar.gz: fbfb78a5ff1e616f0d31af9d581193d5be8e2623615c55e7ceeb785d2e5ade75
5
5
  SHA512:
6
- metadata.gz: 176beb48fdd32511a3516bcd28392cc9b9677b7cc6b1946c25f83111eeb1c0b4602387913bda61a9524c7211d044c1c16cf47d6a48fbadf803145e660d7e03ff
7
- data.tar.gz: ed688acfb83aae1e7906c01daf363f676d72109cb3f29cfb04923288fba3b0af89c1d72d1e544d7887a940bee2fde79b7f5bb35a4a364a88fa728e3f178e77f4
6
+ metadata.gz: 2c1f8760b6579505b15a5866491719002d46adb2a9b7706352f41d626d4cfb813a15540aa699ccd3220333f12ec44fbbbd98f42ec46f8bf862f82595b0dad241
7
+ data.tar.gz: 3bf3d97c16cd4793729b9f03d3891c127f8560a83c06881d3ccd627ae8d3714650721a09e106ad6cc7ba8130300f917e5bc3482e80165a738701fb23419c9494
@@ -2,7 +2,50 @@
2
2
 
3
3
  ## master
4
4
 
5
- ## 0.4.9
5
+ - Add events support to TagProf. ([@palkan][])
6
+
7
+ Example usage:
8
+
9
+ ```sh
10
+ TAG_PROF=type TAG_PROF_EVENT=sql.active_record rspec
11
+ ```
12
+
13
+ - Add logging helpers for Rails. ([@palkan][])
14
+
15
+ - Add ability to track multiple events at the same time with `EventProf`. ([@palkan][])
16
+
17
+ - Add HTML report for `TagProf`. ([@palkan][])
18
+
19
+ Generate HTML report by setting `TAG_PROF_FORMAT` to `html`.
20
+
21
+ - Add `AnyFixture` DSL. ([@palkan][])
22
+
23
+ Example:
24
+
25
+ ```ruby
26
+ # Enable DSL
27
+ using TestProf::AnyFixture::DSL
28
+
29
+ # and then you can use `fixture` method (which is just an alias for `TestProf::AnyFixture.register`)
30
+ before(:all) { fixture(:account) }
31
+
32
+ # You can also use it to fetch the record (instead of storing it in instance variable)
33
+ let(:account) { fixture(:account) }
34
+ ```
35
+
36
+ - Add `AnyFixture` usage report. ([@palkan][])
37
+
38
+ Enable `AnyFixture` usage reporting with:
39
+
40
+ ```ruby
41
+ TestProf::AnyFixture.reporting_enabled = true
42
+ ```
43
+
44
+ - Add `ActiveRecordSharedConnection` recipe. ([@palkan][])
45
+
46
+ - [#70](https://github.com/palkan/test-prof/pull/70) Add `FactoryAllStub` recipe. ([@palkan][])
47
+
48
+ ## 0.4.9 (2018-03-20)
6
49
 
7
50
  - [Fix [#64](https://github.com/palkan/test-prof/issues/64)] Fix dependencies requiring for FactoryDefault. ([@palkan][])
8
51
 
@@ -11,7 +54,7 @@
11
54
  Consider only `example_failed` and `example_passed` to ensure that the `run_time`
12
55
  is available.
13
56
 
14
- ## 0.4.8
57
+ ## 0.4.8 (2018-01-17)
15
58
 
16
59
  - Add `minitest` 5.11 support. ([@palkan][])
17
60
 
@@ -24,36 +67,36 @@
24
67
 
25
68
  Possibly fixes [#47](https://github.com/palkan/test-prof/issues/47).
26
69
 
27
- ## 0.4.7
70
+ ## 0.4.7 (2017-12-25)
28
71
 
29
72
  - [#57](https://github.com/palkan/test-prof/pull/57) Fix RubyProf Printers Support ([@rabotyaga][])
30
73
 
31
- ## 0.4.6
74
+ ## 0.4.6 (2017-12-17)
32
75
 
33
76
  - Upgrade RSpec/AggregateFailures to RuboCop 0.52.0. ([@palkan][])
34
77
 
35
78
  RuboCop < 0.51.0 is not supported anymore.
36
79
 
37
- - [Fixes [#49](https://github.com/palkan/test-prof/issues/49)] Correcly detect RSpec version in `let_it_be`. ([@desoleary][])
80
+ - [Fixes [#49](https://github.com/palkan/test-prof/issues/49)] Correctly detect RSpec version in `let_it_be`. ([@desoleary][])
38
81
 
39
- ## 0.4.5
82
+ ## 0.4.5 (2017-12-09)
40
83
 
41
84
  - Fix circular require in `lib/factory_doctor/minitest`. ([@palkan][])
42
85
 
43
- ## 0.4.4
86
+ ## 0.4.4 (2017-11-08)
44
87
 
45
88
  - [Fixes [#48](https://github.com/palkan/test-prof/issues/48)] Respect RubyProf reports files extensions. ([@palkan][])
46
89
 
47
- ## 0.4.3
90
+ ## 0.4.3 (2017-10-26)
48
91
 
49
92
  - [#46](https://github.com/palkan/test-prof/pull/46) Support FactoryBot, which is [former FactoryGirl](https://github.com/thoughtbot/factory_bot/pull/1051),
50
93
  while maintaining compatibility with latter. ([@Shkrt][])
51
94
 
52
- ## 0.4.2
95
+ ## 0.4.2 (2017-10-23)
53
96
 
54
97
  - Fix bug with multiple `before_all` within one group. ([@palkan][])
55
98
 
56
- ## 0.4.1
99
+ ## 0.4.1 (2017-10-18)
57
100
 
58
101
  - [#44](https://github.com/palkan/test-prof/pull/44) Support older versions of RSpec. ([@palkan][])
59
102
 
@@ -67,7 +110,7 @@ RSpecDissect `let` tracking supports only RSpec 3.3.0+.
67
110
 
68
111
  It is possible now to use Factory Doctor with Minitest
69
112
 
70
- ## 0.4.0
113
+ ## 0.4.0 (2017-10-03)
71
114
 
72
115
  ### Features:
73
116
 
@@ -79,7 +122,7 @@ It is possible now to use Event Prof with Minitest
79
122
 
80
123
  FactoryProf now also accounts objects created by Fabrication gem (in addition to FactoryGirl)
81
124
 
82
- ## 0.3.0
125
+ ## 0.3.0 (2017-09-21)
83
126
 
84
127
  ### Features:
85
128
 
@@ -116,21 +159,21 @@ Just like `let`, but persist the result for the whole group (i.e. `let` + `befor
116
159
 
117
160
  - Use RubyProf `FlatPrinter` by default (was `CallStackPrinter`). ([@palkan][])
118
161
 
119
- ## 0.2.5
162
+ ## 0.2.5 (2017-08-30)
120
163
 
121
164
  - [#16](https://github.com/palkan/test-prof/pull/16) Support Ruby >= 2.2.0 (was >= 2.3.0). ([@palkan][])
122
165
 
123
- ## 0.2.4
166
+ ## 0.2.4 (2017-08-29)
124
167
 
125
168
  - EventProf: Fix regression bug with examples profiling. ([@palkan][])
126
169
 
127
170
  There was a bug when an event occurs before the example has started (e.g. in `before(:context)` hook).
128
171
 
129
- ## 0.2.3
172
+ ## 0.2.3 (2017-08-28)
130
173
 
131
174
  - Minor improvements. ([@palkan][])
132
175
 
133
- ## 0.2.2
176
+ ## 0.2.2 (2017-08-23)
134
177
 
135
178
  - Fix time calculation when Time class is monkey-patched. ([@palkan][])
136
179
 
@@ -139,13 +182,13 @@ use it everywhere.
139
182
 
140
183
  Fixes [#10](https://github.com/palkan/test-prof/issues/10).
141
184
 
142
- ## 0.2.1
185
+ ## 0.2.1 (2017-08-19)
143
186
 
144
187
  - Detect `RSpec` by checking the presence of `RSpec::Core`. ([@palkan][])
145
188
 
146
189
  Fixes [#8](https://github.com/palkan/test-prof/issues/8).
147
190
 
148
- ## 0.2.0
191
+ ## 0.2.0 (2017-08-18)
149
192
 
150
193
  - Ensure output directory exists. ([@danielwestendorf][])
151
194
 
@@ -157,11 +200,11 @@ Ensure output dir exists in `#artifact_path` method.
157
200
 
158
201
  - FactoryDoctor: print success message when no bad examples found. ([@palkan][])
159
202
 
160
- ## 0.1.1
203
+ ## 0.1.1 (2017-08-17)
161
204
 
162
205
  - AnyFixture: clean tables in reverse order to not fail when foreign keys exist. ([@marshall-lee][])
163
206
 
164
- ## 0.1.0
207
+ ## 0.1.0 (2017-08-15)
165
208
 
166
209
  - Initial version. ([@palkan][])
167
210
 
data/README.md CHANGED
@@ -1,9 +1,10 @@
1
- [![Gem Version](https://badge.fury.io/rb/test-prof.svg)](https://rubygems.org/gems/test-prof) [![Build Status](https://travis-ci.org/palkan/test-prof.svg?branch=master)](https://travis-ci.org/palkan/test-prof) [![CircleCI](https://circleci.com/gh/palkan/test-prof.svg?style=svg)](https://circleci.com/gh/palkan/test-prof) [![Code Triagers Badge](https://www.codetriage.com/palkan/test-prof/badges/users.svg)](https://www.codetriage.com/palkan/test-prof)
1
+ [![Gem Version](https://badge.fury.io/rb/test-prof.svg)](https://rubygems.org/gems/test-prof) [![Build Status](https://travis-ci.org/palkan/test-prof.svg?branch=master)](https://travis-ci.org/palkan/test-prof) [![Code Triagers Badge](https://www.codetriage.com/palkan/test-prof/badges/users.svg)](https://www.codetriage.com/palkan/test-prof)
2
+ [![Documentation](https://img.shields.io/badge/docs-link-brightgreen.svg)](https://test-prof.evilmartians.io)
2
3
 
3
4
  # Ruby Tests Profiling Toolbox
4
5
 
5
6
  <img align="right" height="150" width="129"
6
- title="TestProf logo" src="./assets/logo.svg">
7
+ title="TestProf logo" src="./docs/assets/images/logo.svg">
7
8
 
8
9
  TestProf is a collection of different tools to analyze your test suite performance.
9
10
 
@@ -23,11 +24,11 @@ TestProf toolbox aims to help you identify bottlenecks in your test suite. It co
23
24
 
24
25
  - etc.
25
26
 
26
- Of course, we have some [solutions](#tips-and-tricks-or-recipes) for common performance issues too, bundled into the gem.
27
+ Of course, we have some [solutions](https://test-prof.evilmartians.io/#/?id=recipes) for common performance issues too, bundled into the gem.
27
28
 
28
- [![](https://s3-us-west-1.amazonaws.com/vladem/test-prof/TestProf.png)](https://coggle.it/diagram/Wa7tW-87jAAB0ExN)
29
+ [![](./docs/assets/images/coggle.png)](http://bit.ly/test-prof-map)
29
30
 
30
- See [Table of Contents](#table-of-contents) for more.
31
+ 📑 [Documentation](https://test-prof.evilmartians.io)
31
32
 
32
33
  Supported Ruby versions:
33
34
 
@@ -60,65 +61,17 @@ end
60
61
 
61
62
  And that's it)
62
63
 
63
- ## Table of Contents
64
+ ## Usage
64
65
 
65
- Checkout our guides for each specific tool:
66
-
67
- - [RubyProf Integration](https://github.com/palkan/test-prof/tree/master/guides/ruby_prof.md)
68
-
69
- - [StackProf Integration](https://github.com/palkan/test-prof/tree/master/guides/stack_prof.md)
70
-
71
- - [Event Profiler](https://github.com/palkan/test-prof/tree/master/guides/event_prof.md) (e.g. ActiveSupport notifications)
72
-
73
- - [Tag Profiler](https://github.com/palkan/test-prof/tree/master/guides/tag_prof.md)
74
-
75
- - [Factory Doctor](https://github.com/palkan/test-prof/tree/master/guides/factory_doctor.md)
76
-
77
- - [Factory Profiler](https://github.com/palkan/test-prof/tree/master/guides/factory_prof.md)
78
-
79
- - [RSpecDissect Profiler](https://github.com/palkan/test-prof/tree/master/guides/rspec_dissect.md)
80
-
81
- - [RuboCop cops](https://github.com/palkan/test-prof/tree/master/guides/rubocop.md)
82
-
83
- ## Tips and Tricks (or _Recipes_)
84
-
85
- We also want to share some small code tricks which can help you to improve your test suite performance and efficiency:
86
-
87
- - [`before_all` Hook](https://github.com/palkan/test-prof/tree/master/guides/before_all.md)
88
-
89
- - [`let_it_be` Helper](https://github.com/palkan/test-prof/tree/master/guides/let_it_be.md)
90
-
91
- - [AnyFixture](https://github.com/palkan/test-prof/tree/master/guides/any_fixture.md)
92
-
93
- - [FactoryDefault](https://github.com/palkan/test-prof/tree/master/guides/factory_default.md)
94
-
95
- - [RSpec Stamp](https://github.com/palkan/test-prof/tree/master/guides/rspec_stamp.md)
96
-
97
- - [Tests Sampling](https://github.com/palkan/test-prof/tree/master/guides/tests_sampling.md)
98
-
99
- ## Configuration
100
-
101
- TestProf global configuration is used by most of the profilers:
102
-
103
- ```ruby
104
- TestProf.configure do |config|
105
- # the directory to put artifacts (reports) in ("tmp/test_prof" by default)
106
- config.output_dir = "tmp/test_prof"
107
-
108
- # use unique filenames for reports (by simply appending current timestamp)
109
- config.timestamps = true
110
-
111
- # color output
112
- config.color = true
113
- end
114
- ```
66
+ Check out our [docs][].
115
67
 
116
68
  ## What's next?
117
69
 
118
-
119
70
  Have an idea? [Propose](https://github.com/palkan/test-prof/issues/new) a feature request!
120
71
 
121
72
 
122
73
  ## License
123
74
 
124
75
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
76
+
77
+ [docs]: https://test-prof.evilmartians.io
@@ -0,0 +1,447 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>TestProf: TagProf report</title>
5
+ <meta name="viewport" content="width=device-width">
6
+ <meta charset="UTF-8">
7
+ <style>
8
+ html, body {
9
+ height: 100%;
10
+ width: 100%;
11
+ min-width: 300px;
12
+ }
13
+
14
+ body {
15
+ background: #f9f9f9;
16
+ color: #363636;
17
+ font: 18px/30px "Arial",sans-serif;
18
+ margin: 0;
19
+ padding: 0;
20
+ }
21
+
22
+ a {
23
+ text-decoration: none;
24
+ color: #ff5e5e
25
+ }
26
+
27
+ a:visited, a:active{
28
+ color: #ff5e5e;
29
+ }
30
+
31
+ a:hover{
32
+ opacity: 0.8;
33
+ }
34
+
35
+ h1 {
36
+ font-size: 30px;
37
+ line-height: 32px;
38
+ letter-spacing: 2px;
39
+ font-weight: bold;
40
+ padding: 30px 0 30px 0;
41
+ margin: 0;
42
+ }
43
+
44
+ .main {
45
+ height: 100%;
46
+ width: 100%;
47
+ margin: 0;
48
+ padding: 0;
49
+ }
50
+
51
+ header {
52
+ position: relative;
53
+ text-align: center;
54
+ background: #fff;
55
+ overflow: hidden;
56
+ display: flex;
57
+ align-items: center;
58
+ flex-direction: column;
59
+ }
60
+
61
+ .chart {
62
+ display: flex;
63
+ justify-content: center;
64
+ position: relative;
65
+ box-sizing: border-box;
66
+ flex-direction: row;
67
+ padding: 20px;
68
+ }
69
+
70
+ .tooltip {
71
+ background: #eee;
72
+ box-shadow: 0 0 5px #999999;
73
+ color: #333;
74
+ display: none;
75
+ font-size: 14px;
76
+ left: 130px;
77
+ padding: 10px;
78
+ position: absolute;
79
+ text-align: left;
80
+ top: 95px;
81
+ z-index: 10;
82
+ }
83
+
84
+ .tooltip .label.active {
85
+ color: #ff5e5e;
86
+ }
87
+
88
+ .tooltip .value-label {
89
+ font-weight: bold;
90
+ margin-right: 10px;
91
+ }
92
+
93
+ .legend, .totals {
94
+ font-size: 14px;
95
+ }
96
+
97
+ #selector {
98
+ font-size: 14px;
99
+ display: flex;
100
+ align-items: flex-start;
101
+ position: relative;
102
+ box-sizing: border-box;
103
+ flex-direction: column;
104
+ padding: 20px;
105
+ }
106
+
107
+ rect {
108
+ stroke-width: 2;
109
+ }
110
+
111
+ rect.disabled { /* NEW */
112
+ fill: transparent !important; /* NEW */
113
+ }
114
+
115
+ .d3-donut-path {
116
+ transition: opacity 200ms;
117
+ opacity: 1;
118
+ cursor: pointer;
119
+ }
120
+
121
+ .d3-donut-path:hover {
122
+ opacity: 0.8;
123
+ }
124
+
125
+ .humanoids__link {
126
+ width: 130px;
127
+ }
128
+
129
+ .humanoids{
130
+ position: absolute;
131
+ bottom: -23px;
132
+ width: 150px;
133
+ height: 60px;
134
+ margin: 0 auto 0 -10px;
135
+ font-size: 0;
136
+ transform: scale(0.6);
137
+ }
138
+ .humanoids circle{
139
+ transform: scaleY(1);
140
+ transform-origin: 50%;
141
+ animation-duration: 8s;
142
+ animation-name: humanoids-blink;
143
+ animation-iteration-count: infinite;
144
+ }
145
+ .humanoids svg{
146
+ height: 60px;
147
+ }
148
+ .humanoids__human, .humanoids__martian{
149
+ position: absolute;
150
+ width: 80px;
151
+ height: 90px;
152
+ transition: transform 300ms cubic-bezier(0.68, -0.55, 0.265, 1.55);
153
+ transform: translateY(0);
154
+ }
155
+ .humanoids__human:hover, .humanoids__martian:hover{
156
+ transform: translateY(-12px);
157
+ }
158
+ .humanoids__martian{
159
+ left: 0;
160
+ }
161
+ .humanoids__human{
162
+ right: 0;
163
+ }
164
+ .humanoids__human circle{
165
+ animation-delay: 0.5s;
166
+ }
167
+
168
+ [name="root"] .label {
169
+ text-align: center;
170
+ }
171
+ </style>
172
+ </head>
173
+ <body>
174
+ <div class="main">
175
+ <header>
176
+ <h1>TagProf Report</h1>
177
+ <a class="humanoids__link" href="https://evilmartians.com">
178
+ <div class="humanoids">
179
+ <div class="humanoids__martian"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 108 92"><path fill="#f64242" d="M26 88L8 78l18-10"/><path fill="#f64242" d="M94 92v-6H44c-5.5 0-10-4.5-10-10s4.5-10 10-10h50V32c0-14-7.9-22-22-22H48c-14.1 0-22 8-22 22v60h68z"/><circle fill="#FFF" cx="48" cy="50" r="8"/><circle fill="#FFF" cx="72" cy="50" r="8"/><circle fill="#BF6C35" cx="48" cy="50" r="4"/><circle fill="#BF6C35" cx="72" cy="50" r="4"/><g fill="#663F4C"><path d="M48 60c-5.5 0-10-4.5-10-10s4.5-10 10-10 10 4.5 10 10-4.5 10-10 10zm0-16c-3.3 0-6 2.7-6 6s2.7 6 6 6 6-2.7 6-6-2.7-6-6-6zM82 50c0 5.5-4.5 10-10 10s-10-4.5-10-10 4.5-10 10-10 10 4.5 10 10zm-16 0c0 3.3 2.7 6 6 6s6-2.7 6-6-2.7-6-6-6-6 2.7-6 6z"/><path d="M102 8c-3.8 0-6-2.2-6-6 0-1.1-.9-2-2-2s-2 .9-2 2c0 2.2.5 4.1 1.5 5.7L88.2 13c-4-3.3-9.5-5-16.2-5H48c-6.7 0-12.2 1.7-16.2 4.9l-5.3-5.3C27.5 6.1 28 4.2 28 2c0-1.1-.9-2-2-2s-2 .9-2 2c0 3.8-2.2 6-6 6-1.1 0-2 .9-2 2s.9 2 2 2c2.2 0 4.1-.5 5.7-1.5l5.3 5.3c-3.2 4-4.9 9.5-4.9 16.2v34.8L3.9 78 24 89v3h4V32c0-12.9 7.1-20 20-20h24c12.9 0 20 7.1 20 20v32H44c-6.6 0-12 5.4-12 12s5.4 12 12 12h48v4h4v-8h-2l-4-8-4 8h-4l-4-8-4 8h-4l-4-8-4 8h-4l-4-8-4 8h-4l-4-8-3.1 6.2C37.1 80.7 36 78.5 36 76c0-4.4 3.6-8 8-8l4 8 4-8h4l4 8 4-8h4l4 8 4-8h4l4 8 4-8h8V32c0-6.7-1.7-12.2-4.9-16.2l5.3-5.3c1.6 1 3.5 1.5 5.7 1.5 1.1 0 2-.9 2-2s-1-2-2.1-2zM24 84.4L12.1 78 24 71.4v13z"/></g></svg></div>
180
+ <div class="humanoids__human"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 108 92"><path fill="#FFFFFF" d="M14 62v30h68v-6H32c-5.5 0-10-4.5-10-10s4.5-10 10-10h50v-4c4 0 8-3.6 8-8s-4-8-8-8V32c0-14-7.9-22-22-22H36c-14.1 0-22 8-22 22v14c-4 0-8 3.6-8 8s4 8 8 8z"/><circle fill="#FFF" cx="36" cy="50" r="8"/><circle fill="#FFF" cx="60" cy="50" r="8"/><circle fill="#f64242" cx="36" cy="50" r="4"/><circle fill="#f64242" cx="60" cy="50" r="4"/><path fill="#f64242" d="M60 10H36c-14.1 0-22 8-22 22v2l4-4 6 6 6-6 6 6 6-6 6 6 6-6 6 6 6-6 6 6 6-6 4 4v-2c0-14-7.9-22-22-22z"/><g fill="#663F4C"><path d="M36 60c-5.5 0-10-4.5-10-10s4.5-10 10-10 10 4.5 10 10-4.5 10-10 10zm0-16c-3.3 0-6 2.7-6 6s2.7 6 6 6 6-2.7 6-6-2.7-6-6-6zM60 60c-5.5 0-10-4.5-10-10s4.5-10 10-10 10 4.5 10 10-4.5 10-10 10zm0-16c-3.3 0-6 2.7-6 6s2.7 6 6 6 6-2.7 6-6-2.7-6-6-6z"/><path d="M12 63.8V92h4V32c0-12.9 7.1-20 20-20h24c12.9 0 20 7.1 20 20v32H32c-6.6 0-12 5.4-12 12s5.4 12 12 12h48v4h4v-8H74v-1c0-1.7-1.3-3-3-3s-3 1.3-3 3v1h-6v-1c0-1.7-1.3-3-3-3s-3 1.3-3 3v1h-6v-1c0-1.7-1.3-3-3-3s-3 1.3-3 3v1h-6v-1c0-1.7-1.3-3-3-3s-3 1.3-3 3v1c-4 0-8-3.6-8-8s3.6-8 8-8h6v1c0 1.7 1.3 3 3 3s3-1.3 3-3v-1h6v1c0 1.7 1.3 3 3 3s3-1.3 3-3v-1h6v1c0 1.7 1.3 3 3 3s3-1.3 3-3v-1h6v1c0 1.7 1.3 3 3 3s3-1.3 3-3v-1h4v-4.2c5-.9 8-5 8-9.8s-3-8.9-8-9.8V32c0-15.3-8.7-24-24-24H36c-15.3 0-24 8.7-24 24v12.2c-5 .9-8 5-8 9.8s3 8.9 8 9.8zm72-15.5c2 .8 4 3 4 5.7s-2 4.8-4 5.7V48.3zm-72 0v11.3c-2-.8-4-3-4-5.7s2-4.7 4-5.6z"/></g></svg></div>
181
+ </div>
182
+ </a>
183
+ </header>
184
+ <div class="chart">
185
+ <form id="selector">
186
+ </form>
187
+ <div id="chart">
188
+ </div>
189
+ <div id="legend">
190
+ </div>
191
+ </div>
192
+ </div>
193
+
194
+ <script type="text/javascript" src="src/d3.v4.min.js"></script>
195
+
196
+ <script>
197
+ var report_data;
198
+ var width = 360;
199
+ var legendWidth = 240;
200
+ var height = 360;
201
+ var radius = Math.min(width, height) / 2;
202
+ var donutWidth = 75;
203
+ var legendRectSize = 18;
204
+ var legendSpacing = 4;
205
+
206
+ var color = d3.scaleOrdinal(d3.schemeCategory20);
207
+
208
+ function pad(n, size) {
209
+ if(size == void 0) size = 2;
210
+
211
+ var str = n.toString();
212
+
213
+ while(str.length < size){
214
+ str = "0" + str;
215
+ }
216
+
217
+ return str;
218
+ }
219
+
220
+ function duration(t) {
221
+ return pad(t / 60|0) + ':' + pad((t % 60) |0) + '.' + pad(((t * 1000) % 1000)|0, 3);
222
+ }
223
+
224
+ var svg = d3.select('#chart')
225
+ .append('svg')
226
+ .attr('width', width)
227
+ .attr('height', height)
228
+ .append('g')
229
+ .attr('transform', 'translate(' + (width / 2) +
230
+ ',' + (height / 2) + ')');
231
+
232
+ var arc = d3.arc()
233
+ .innerRadius(radius - donutWidth)
234
+ .outerRadius(radius);
235
+
236
+ var labelArc = d3.arc()
237
+ .outerRadius(radius - donutWidth)
238
+ .innerRadius(radius);
239
+
240
+ var pie = d3.pie()
241
+ .value(function(d) { return d.time; })
242
+ .sort(function(a, b) {
243
+ return b.time - a.time;
244
+ });
245
+
246
+ var tooltip = d3.select('#chart')
247
+ .append('div')
248
+ .attr('class', 'tooltip');
249
+
250
+ tooltip.append('div')
251
+ .attr('class', 'label');
252
+
253
+ tooltip.append('div')
254
+ .attr('class', 'stats');
255
+
256
+ // var report_data = JSON.parse('{/**REPORT-DATA**/}');
257
+ var report_data = {
258
+ tag: "type",
259
+ events: [
260
+ "sql.active_record"
261
+ ],
262
+ data: [
263
+ { value: "model", count: 123, time: 324.2, "sql.active_record": 21.54 },
264
+ { value: "controller", count: 44, time: 514, "sql.active_record": 121 },
265
+ { value: "job", count: 33, time: 122.2 },
266
+ { value: "__unknown__", count: 2, time: 44, "sql.active_record": 0 }
267
+ ]
268
+ };
269
+
270
+ var dataset = report_data.data;
271
+
272
+ dataset.sort(function(a, b){
273
+ return b.time - a.time;
274
+ });
275
+
276
+ var totals = {};
277
+
278
+ ["count", "time"].concat(report_data.events).forEach(function(event) {
279
+ totals[event] = d3.sum(dataset.map(function(d){
280
+ return d[event] || 0;
281
+ }));
282
+ });
283
+
284
+ dataset.forEach(function(d) {
285
+ d.labels = {};
286
+
287
+ if (d.value == "__unknown__") {
288
+ d.color = "#cccccc";
289
+ };
290
+
291
+ d.labels.count = {
292
+ value: d.count,
293
+ percent: Math.round(1000 * d.count / totals.count) / 10
294
+ };
295
+
296
+ ["time"].concat(report_data.events).forEach(function(event){
297
+ d.labels[event] = {
298
+ value: duration(d[event]),
299
+ percent: d[event] ? Math.round(1000 * d[event] / totals[event]) / 10 : 0
300
+ }
301
+ });
302
+ });
303
+
304
+ var path = svg.selectAll('path')
305
+ .data(pie(dataset))
306
+ .enter()
307
+ .append('path')
308
+ .attr('d', arc)
309
+ .attr('class', 'd3-donut-path')
310
+ .attr('fill', function(d, i) {
311
+ return d.data.color || color(d.data.value);
312
+ })
313
+ .each(function(d) { this._current = d; });
314
+
315
+ var currentMeasure = "time";
316
+
317
+ pie.value(function(d) {
318
+ return d[currentMeasure];
319
+ });
320
+
321
+ path.on('mouseover', function(d) {
322
+ tooltip.select('.label').html("<span class='value-label'>" + report_data.tag + ":</span><span class='value-data'>" + d.data.value + "</span>");
323
+
324
+ var str = "";
325
+
326
+ ["count", "time"].concat(report_data.events).forEach(function(event) {
327
+ str += "<div class='label " + (event == currentMeasure ? 'active' : '') + "'><span class='value-label'>" + event + ":</span><span class='value-data'>" + d.data.labels[event].value + " ("+d.data.labels[event].percent + "%)</span></div>";
328
+ });
329
+ tooltip.select('.stats').html(str);
330
+ tooltip.style('display', 'block');
331
+ });
332
+
333
+ path.on('mousemove', function(d) {
334
+ tooltip.style('top', (d3.event.layerY + 10) + 'px')
335
+ .style('left', (d3.event.layerX + 10) + 'px');
336
+ });
337
+
338
+ path.on('mouseout', function() {
339
+ tooltip.style('display', 'none');
340
+ });
341
+
342
+ if(report_data.events && report_data.events.length) {
343
+ var selectOptions = ["time"].concat(report_data.events);
344
+
345
+ var selector = d3.select("form").selectAll("label")
346
+ .data(selectOptions)
347
+ .enter().append("label");
348
+
349
+ selector.append("input")
350
+ .attr("type", "radio")
351
+ .attr("name", "measurement")
352
+ .attr("value", function(d) { return d; })
353
+ .property("checked", function(d, i) { return i === 0 });
354
+
355
+ d3.select("form").style('width', legendWidth + 'px');
356
+
357
+ d3.selectAll(("input[name='measurement']")).on("change", function(e) {
358
+ render(e);
359
+ });
360
+
361
+ selector.append("span")
362
+ .text(function(d) { return d; });
363
+ } else {
364
+ d3.select("form").remove();
365
+ }
366
+
367
+ var legendContainer = d3.select('#legend')
368
+ .append('svg')
369
+ .attr('height', height)
370
+ .attr('width', legendWidth);
371
+
372
+ var legend = legendContainer.selectAll('legend')
373
+ .data(color.domain())
374
+ .enter()
375
+ .append('g')
376
+ .attr('class', 'legend')
377
+ .attr('transform', function(d, i) {
378
+ var height = legendRectSize + legendSpacing;
379
+ var horz = 2 * legendRectSize;
380
+ var vert = i * height;
381
+ return 'translate(' + horz + ',' + vert + ')';
382
+ });
383
+
384
+ legend.append('rect')
385
+ .attr('width', legendRectSize)
386
+ .attr('height', legendRectSize)
387
+ .style('fill', color)
388
+ .style('stroke', color);
389
+
390
+ legend.append('text')
391
+ .attr('x', legendRectSize + legendSpacing)
392
+ .attr('y', legendRectSize - legendSpacing)
393
+ .text(function(d) { return d; });
394
+
395
+ var totalsContainer = svg.append('g')
396
+ .attr('class', 'totals')
397
+ .attr('transform', 'translate(-' + (radius - 100) +', -'+ (radius - 120) + ')');
398
+
399
+ var totalsArray = d3.keys(totals).map(function(key){
400
+ if(key == "count"){
401
+ return { key: key, value: totals[key] };
402
+ } else {
403
+ return { key: key, value: duration(totals[key]) };
404
+ }
405
+ });
406
+
407
+ totalsContainer.append('text')
408
+ .text("TOTALS")
409
+ .attr('x', 50);
410
+
411
+ var totalsLegend = totalsContainer.selectAll('total')
412
+ .data(totalsArray)
413
+ .enter()
414
+ .append('g')
415
+ .attr('class', 'total')
416
+ .attr('transform', function(d, i) {
417
+ var height = legendRectSize + legendSpacing;
418
+ var horz = 0;
419
+ var vert = i * height + 20;
420
+ return 'translate(' + horz + ',' + vert + ')';
421
+ });
422
+
423
+ totalsLegend.append('text')
424
+ .attr('x', 0)
425
+ .attr('y', legendRectSize - legendSpacing)
426
+ .text(function(d) {
427
+ return d.key + ": " + d.value;
428
+ });
429
+
430
+ var render = function(measurement) {
431
+ currentMeasure = measurement;
432
+
433
+ path = path.data(pie(dataset));
434
+
435
+ path.transition()
436
+ .duration(750)
437
+ .attrTween('d', function(d) {
438
+ var interpolate = d3.interpolate(this._current, d);
439
+ this._current = interpolate(0);
440
+ return function(t) {
441
+ return arc(interpolate(t));
442
+ };
443
+ });
444
+ }
445
+ </script>
446
+ </body>
447
+ </html>