xc_metrics_aggregator 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 61c0dba58870efc2783f660b792ac84bdf9e876107b6f42dd65c6cc004ca68ca
4
+ data.tar.gz: 9921f07076290282093d572302b5afdf0f9950e08dcedb2c99b3c9ce9cc7bf84
5
+ SHA512:
6
+ metadata.gz: 1c69738d9955e674bed8628b262fb8a704018eafce81e9046f97741d0d0c0c29848026f60f4761077345bdbd3344f73b9b676eac08d782d8da7d86f3a319b49c
7
+ data.tar.gz: 39f88c73f3031c1f6641e44703d6051fa70aeae62ff93ba33c74304e25bfb5869688e0339ebc1cc57de5d3ba8509a32ecd4ff34c69481fe16d0445214df69449
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at kazuhiro.hayashi.49@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in xc_metrics_aggregator.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,36 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ xc_metrics_aggregator (0.1.0)
5
+ ascii_charts
6
+ etc
7
+ terminal-table
8
+ thor
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ ascii_charts (0.9.1)
14
+ chronic_duration (0.10.6)
15
+ numerizer (~> 0.1.1)
16
+ etc (1.1.0)
17
+ numerizer (0.1.1)
18
+ rake (10.5.0)
19
+ spec (5.3.4)
20
+ chronic_duration (~> 0.10.2)
21
+ terminal-table (1.8.0)
22
+ unicode-display_width (~> 1.1, >= 1.1.1)
23
+ thor (1.0.1)
24
+ unicode-display_width (1.6.1)
25
+
26
+ PLATFORMS
27
+ ruby
28
+
29
+ DEPENDENCIES
30
+ bundler (~> 2.0)
31
+ rake (~> 10.0)
32
+ spec
33
+ xc_metrics_aggregator!
34
+
35
+ BUNDLED WITH
36
+ 2.1.2
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Yahoo Japan Corporation
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
data/README.md ADDED
@@ -0,0 +1,244 @@
1
+ ![](./assets/XCMetricsAggregator.png)
2
+
3
+ ## What's this?
4
+
5
+ XCMetricsAggregator aggregates metrics data from [Xcode Metrics Organizer](https://help.apple.com/xcode/mac/current/#/devb642b28ac) with AppleScript.
6
+ It also processes the raw data on your mac device to show the organized tables and charts.
7
+
8
+ You can check how to use Xcode Metrics Organizer in WWDC 2019 session below.
9
+
10
+ - [Improving Battery Life and Performance - WWDC 2019](https://developer.apple.com/videos/play/wwdc2019/417/)
11
+
12
+ ## Setup
13
+
14
+ 1. Install Xcode 11.x from App Store or the following link
15
+
16
+ https://developer.apple.com/download/more/
17
+
18
+ 2. Login with your Apple ID on Accounts preferences in Xcode
19
+
20
+ <img width="400" alt="スクリーンショット 0002-02-09 18 32 32" src="https://user-images.githubusercontent.com/18320004/74100072-1b04b300-4b6e-11ea-97e0-32239898846d.png">
21
+
22
+ 3. check Accessibility setting in Security & Privacy. Script Editor, System Events and Terminal needs to be checked.
23
+
24
+ <img width="400" alt="Screen Shot 0002-02-09 at 19 28 25" src="https://user-images.githubusercontent.com/18320004/74100455-789afe80-4b72-11ea-9cef-4ea1704edeac.png">
25
+
26
+
27
+ ## Getting Started
28
+ There are 2 kinds of command.
29
+
30
+ - manupulating Xcode automatically
31
+ - processing raw json files for metrics data
32
+
33
+ "crowl" command download the json files with Xcode, and the others process them.
34
+
35
+ ### Downloading metrics data
36
+ ```
37
+ bundle exec exe/xcmagg crowl
38
+ ```
39
+
40
+ This command launches Xcode automatically, and operates the mouse.
41
+
42
+ If you interrupt the crowling, input Ctrl + C on the terminal.
43
+
44
+
45
+ ### Application lookup in metrics data
46
+ ```
47
+ bundle exec exe/xcmagg apps
48
+ ```
49
+
50
+ ```
51
+ +--------------------------------------------+---------------------+
52
+ | available app list |
53
+ +--------------------------------------------+---------------------+
54
+ | bundle id | status |
55
+ +--------------------------------------------+---------------------+
56
+ | yourapp1 | has metrics |
57
+ | yourapp2 | has metrics |
58
+ +--------------------------------------------+---------------------+
59
+ ```
60
+
61
+ ### Available devices for your apps
62
+ ```
63
+ bundle exec exe/xcmagg devices --b yourapp1,yourapp2
64
+ ```
65
+
66
+ ```
67
+ +--------------+---------------------------------------+------------+
68
+ | yourapp1 |
69
+ +--------------+---------------------------------------+------------+
70
+ | kind | device | id |
71
+ +--------------+---------------------------------------+------------+
72
+ | iPhone (All) | iPhone 8 | iPhone10,1 |
73
+ | | iPhone X | iPhone10,3 |
74
+ | | iPhone Xs | iPhone11,2 |
75
+ | | iPhone Xs Max | iPhone11,6 |
76
+ | | iPhone Xʀ | iPhone11,8 |
77
+ | | iPhone 11 | iPhone12,1 |
78
+ +--------------+---------------------------------------+------------+
79
+ | iPad (All) | iPad (6th Generation) | iPad7,5 |
80
+ | | iPad (7th generation) | iPad7,11 |
81
+ | | iPad Pro (11-inch) | iPad8,1 |
82
+ | | iPad Pro (12.9-inch) (3rd Generation) | iPad8,5 |
83
+ | | iPad mini (5th generation) | iPad11,1 |
84
+ | | iPad Air (3rd generation) | iPad11,3 |
85
+ +--------------+---------------------------------------+------------+
86
+
87
+ +--------------+---------------------------------------+------------+
88
+ | yourapp2 |
89
+ +--------------+---------------------------------------+------------+
90
+ | kind | device | id |
91
+ +--------------+---------------------------------------+------------+
92
+ | iPhone (All) | iPhone 8 | iPhone10,1 |
93
+ | | iPhone X | iPhone10,3 |
94
+ | | iPhone Xs | iPhone11,2 |
95
+ | | iPhone Xs Max | iPhone11,6 |
96
+ | | iPhone Xʀ | iPhone11,8 |
97
+ | | iPhone 11 | iPhone12,1 |
98
+ +--------------+---------------------------------------+------------+
99
+ | iPad (All) | iPad (6th Generation) | iPad7,5 |
100
+ | | iPad (7th generation) | iPad7,11 |
101
+ | | iPad Pro (11-inch) | iPad8,1 |
102
+ | | iPad Pro (12.9-inch) (3rd Generation) | iPad8,5 |
103
+ | | iPad mini (5th generation) | iPad11,1 |
104
+ | | iPad Air (3rd generation) | iPad11,3 |
105
+ +--------------+---------------------------------------+------------+
106
+ ```
107
+
108
+ ### Available percentiles for your apps
109
+ ```
110
+ bundle exec exe/xcmagg percentiles --b yourapp1,yourapp2
111
+ ```
112
+
113
+ ```
114
+ +----------------+--------------------------------------------+
115
+ | yourapp1 |
116
+ +----------------+--------------------------------------------+
117
+ | percentile | id |
118
+ +----------------+--------------------------------------------+
119
+ | Typical | com.apple.dt.metrics.percentile.fifty |
120
+ | Top Percentile | com.apple.dt.metrics.percentile.ninetyFive |
121
+ +----------------+--------------------------------------------+
122
+
123
+ +----------------+--------------------------------------------+
124
+ | yourapp2 |
125
+ +----------------+--------------------------------------------+
126
+ | percentile | id |
127
+ +----------------+--------------------------------------------+
128
+ | Typical | com.apple.dt.metrics.percentile.fifty |
129
+ | Top Percentile | com.apple.dt.metrics.percentile.ninetyFive |
130
+ +----------------+--------------------------------------------+
131
+ ```
132
+
133
+ ### Launch time metrics for an app
134
+ ```
135
+ bundle exec exe/xcmagg metrics -s launchTime -b yourapp1 -d iPhone11,6
136
+ ```
137
+
138
+ ```
139
+ +-------+-------------+----------+----------------+
140
+ | Label | Version | Device | Percentile |
141
+ +-------+-------------+----------+----------------+
142
+ | 0 | 1.0.0 | iPhone 8 | Typical |
143
+ | 1 | 1.1.0 | iPhone 8 | Typical |
144
+ | 2 | 1.3.0 | iPhone 8 | Typical |
145
+ | 3 | 1.4.0 | iPhone 8 | Typical |
146
+ | 4 | 1.5.0 | iPhone 8 | Typical |
147
+ | 5 | 2.0.0 | iPhone 8 | Typical |
148
+ +-------+-------------+----------+----------------+
149
+
150
+ 700.0|
151
+ 600.0|
152
+ 500.0| *
153
+ 400.0| *
154
+ 300.0| * * * * *
155
+ 200.0| * * * * * *
156
+ 100.0| * * * * * *
157
+ 0.0+-------------------
158
+ 0 1 2 3 4 5
159
+
160
+ Unit: ms
161
+
162
+ ```
163
+
164
+
165
+ ### Compare launch times between your apps
166
+ ```
167
+ bundle exec exe/xcmagg latest -s launchTime -d iPhone11,6 -p com.apple.dt.metrics.percentile.ninetyFive
168
+ ```
169
+
170
+ ```
171
+ +-------+-----------------------------------+-------------+
172
+ | Label | Bundle ID | Version |
173
+ +-------+-----------------------------------+-------------+
174
+ | 0 | yourapp1 | 2.0.0 |
175
+ | 1 | yourapp2 | 1.1.0 |
176
+ +-------+-----------------------------------+-------------+
177
+
178
+ 700.0| *
179
+ 600.0| *
180
+ 500.0| *
181
+ 400.0| *
182
+ 300.0| * *
183
+ 200.0| * *
184
+ 100.0| * *
185
+ 0.0+------
186
+ 0 1
187
+
188
+ Unit: ms
189
+
190
+ ```
191
+
192
+ ### Send the metrics to your log server with CSV format
193
+ ```
194
+ bundle exec exe/xcmagg latest -s launchTime -d iPhone11,6 -p com.apple.dt.metrics.percentile.ninetyFive --f csv
195
+ ```
196
+
197
+ ```
198
+ Label,Bundle ID,Version,Point
199
+ 0,yourapp1,2.0.0,698
200
+ 1,yourapp2,1.1.0,300
201
+ ```
202
+
203
+ ## Run with Launchd to see changes over time
204
+
205
+ If you would like to run script periodically, you can use launchd.
206
+
207
+ Create plist file to `~/Library/LaunchAgents/xcode-metrics.plist`.
208
+
209
+ ```
210
+ <?xml version="1.0" encoding="UTF-8"?>
211
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
212
+ <plist version="1.0">
213
+ <dict>
214
+ <key>Label</key>
215
+ <string>xcode-metrics</string>
216
+ <key>ProgramArguments</key>
217
+ <array>
218
+ <string>/bin/sh</string>
219
+ <string>/your/path/xcode-metrics-automation/run.sh</string>
220
+ </array>
221
+ <key>StartCalendarInterval</key>
222
+ <dict>
223
+ <key>Hour</key>
224
+ <integer>12</integer>
225
+ <key>Minute</key>
226
+ <integer>10</integer>
227
+ </dict>
228
+ <key>StandardOutPath</key>
229
+ <string>/tmp/xcode-metrics.out</string>
230
+ <key>StandardErrorPath</key>
231
+ <string>/tmp/xcode-metrics.err</string>
232
+ </dict>
233
+ </plist>
234
+ ```
235
+
236
+ Run this command in Terminal to enable the setting.
237
+
238
+ ```
239
+ launchctl load ~/Library/LaunchAgents/xcode-metrics.plist
240
+ ```
241
+
242
+ ## License
243
+
244
+ This software is released under the MIT License, see LICENSE.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
Binary file
data/bin/bundle ADDED
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'bundle' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "rubygems"
12
+
13
+ m = Module.new do
14
+ module_function
15
+
16
+ def invoked_as_script?
17
+ File.expand_path($0) == File.expand_path(__FILE__)
18
+ end
19
+
20
+ def env_var_version
21
+ ENV["BUNDLER_VERSION"]
22
+ end
23
+
24
+ def cli_arg_version
25
+ return unless invoked_as_script? # don't want to hijack other binstubs
26
+ return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
27
+ bundler_version = nil
28
+ update_index = nil
29
+ ARGV.each_with_index do |a, i|
30
+ if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
31
+ bundler_version = a
32
+ end
33
+ next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
34
+ bundler_version = $1
35
+ update_index = i
36
+ end
37
+ bundler_version
38
+ end
39
+
40
+ def gemfile
41
+ gemfile = ENV["BUNDLE_GEMFILE"]
42
+ return gemfile if gemfile && !gemfile.empty?
43
+
44
+ File.expand_path("../../Gemfile", __FILE__)
45
+ end
46
+
47
+ def lockfile
48
+ lockfile =
49
+ case File.basename(gemfile)
50
+ when "gems.rb" then gemfile.sub(/\.rb$/, gemfile)
51
+ else "#{gemfile}.lock"
52
+ end
53
+ File.expand_path(lockfile)
54
+ end
55
+
56
+ def lockfile_version
57
+ return unless File.file?(lockfile)
58
+ lockfile_contents = File.read(lockfile)
59
+ return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
60
+ Regexp.last_match(1)
61
+ end
62
+
63
+ def bundler_version
64
+ @bundler_version ||=
65
+ env_var_version || cli_arg_version ||
66
+ lockfile_version
67
+ end
68
+
69
+ def bundler_requirement
70
+ return "#{Gem::Requirement.default}.a" unless bundler_version
71
+
72
+ bundler_gem_version = Gem::Version.new(bundler_version)
73
+
74
+ requirement = bundler_gem_version.approximate_recommendation
75
+
76
+ return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0")
77
+
78
+ requirement += ".a" if bundler_gem_version.prerelease?
79
+
80
+ requirement
81
+ end
82
+
83
+ def load_bundler!
84
+ ENV["BUNDLE_GEMFILE"] ||= gemfile
85
+
86
+ activate_bundler
87
+ end
88
+
89
+ def activate_bundler
90
+ gem_error = activation_error_handling do
91
+ gem "bundler", bundler_requirement
92
+ end
93
+ return if gem_error.nil?
94
+ require_error = activation_error_handling do
95
+ require "bundler/version"
96
+ end
97
+ return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
98
+ warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
99
+ exit 42
100
+ end
101
+
102
+ def activation_error_handling
103
+ yield
104
+ nil
105
+ rescue StandardError, LoadError => e
106
+ e
107
+ end
108
+ end
109
+
110
+ m.load_bundler!
111
+
112
+ if m.invoked_as_script?
113
+ load Gem.bin_path("bundler", "bundle")
114
+ end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "xc_metrics_aggregator"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/bin/xcmagg ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'xc_metrics_aggregator'
4
+
5
+ XcMetricsAggregator::CLI.start
@@ -0,0 +1,19 @@
1
+ require 'open3'
2
+
3
+ module XcMetricsAggregator
4
+ ROOT_DIR = File.expand_path("..", __dir__)
5
+ APPLE_SCRIPT_EXECUTION = "osascript"
6
+ APPLE_SCRIPT_FILE = "xcode_metrics_automation.applescript"
7
+
8
+ class Crawler
9
+ def self.execute
10
+ script_fullpath = File.join(ROOT_DIR, APPLE_SCRIPT_FILE)
11
+ command ="#{APPLE_SCRIPT_EXECUTION} #{script_fullpath}"
12
+ Open3.popen3(command) do |i, o, e, w|
13
+ o.each do |line| puts line end
14
+ e.each do |line| puts line end
15
+ puts w.value
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,105 @@
1
+ require 'terminal-table'
2
+ require 'xc_metrics_aggregator/structure/structure'
3
+ require 'xc_metrics_aggregator/formatter/output_format'
4
+
5
+
6
+ module XcMetricsAggregator
7
+ class Formatter
8
+ def self.get_formatter(format)
9
+ case format
10
+ when OutputFormat::CSV
11
+ CSVFormatter.new
12
+ when OutputFormat::ASCII
13
+ ASCIIFormatter.new
14
+ end
15
+ end
16
+
17
+ def format(data)
18
+ case data
19
+ when TableStructure
20
+ format_table(data)
21
+ when ChartStructure
22
+ format_chart(data)
23
+ end
24
+ end
25
+
26
+ def format_table(data)
27
+ end
28
+
29
+ def format_chart(data)
30
+ end
31
+ end
32
+
33
+ class CSVFormatter < Formatter
34
+ def format_table(data)
35
+ output = data.headings.join(',') + "\n"
36
+ data.rows.each do |row|
37
+ if row == :separator
38
+ next
39
+ end
40
+
41
+ sub_rows = row.map { |e| e.split("\n") }
42
+ max_sub_row = sub_rows.max { |a, b| a.count <=> b.count }
43
+ expaneded_sub_rows = sub_rows.map do |sub_row|
44
+ unit = max_sub_row.count / sub_row.count
45
+ mod = max_sub_row.count.modulo sub_row.count
46
+ expanded_sub_row = sub_row.map { |sub_row_e| Array.new(unit, sub_row_e) }.flatten
47
+ if sub_row.last
48
+ expanded_sub_row += Array.new(mod, sub_row.last)
49
+ end
50
+ expanded_sub_row
51
+ end
52
+ output += expaneded_sub_rows
53
+ .transpose
54
+ .map do |row|
55
+ row.map{ |e| e.include?(",") ? "\"#{e}\"" : e }
56
+ .join(',')
57
+ end
58
+ .join("\n")
59
+ output += "\n"
60
+ end
61
+ output
62
+ end
63
+
64
+ def format_chart(data)
65
+ csv_head = data.series.headings + ["Point"]
66
+ output = csv_head.join(',') + "\n"
67
+
68
+ rows = data.series.rows.map do |line|
69
+ label = line.first
70
+ unless label
71
+ next
72
+ end
73
+
74
+ sample = data.samples.find {|sample| sample.first == label }
75
+ unless sample
76
+ next
77
+ end
78
+
79
+ line.append(sample.last)
80
+ end
81
+
82
+ output += rows.map do |row|
83
+ row.map{ |e| e.to_s.include?(",") ? "\"#{e}\"" : e }
84
+ .join(',')
85
+ end
86
+ .join("\n")
87
+ output += "\n"
88
+ output
89
+ end
90
+ end
91
+
92
+ class ASCIIFormatter < Formatter
93
+ def format_table(data)
94
+ Terminal::Table.new rows: data.rows, headings: data.headings, title: data.title
95
+ end
96
+
97
+ def format_chart(data)
98
+ output = Terminal::Table.new(rows: data.series.rows, headings: data.series.headings).to_s
99
+ output += "\n"
100
+ output += AsciiCharts::Cartesian.new(data.samples, bar: true, hide_zero: true).draw.to_s
101
+ output += "\n"
102
+ output += "Unit: #{data.unit}\n\n"
103
+ end
104
+ end
105
+ end