appsignal_report 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +17 -0
- data/LICENSE +21 -0
- data/README.md +153 -0
- data/bin/appsignal_report_deploy +72 -0
- data/bin/appsignal_report_weekly +69 -0
- data/lib/appsignal_report/base_report.rb +160 -0
- data/lib/appsignal_report/deploy_report.rb +64 -0
- data/lib/appsignal_report/slack_message.rb +61 -0
- data/lib/appsignal_report/version.rb +3 -0
- data/lib/appsignal_report/weekly_report.rb +44 -0
- data/lib/appsignal_report.rb +10 -0
- metadata +84 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d4d4507b1ca8ebdd01e7cb9f06ce189d3c93c7e8
|
4
|
+
data.tar.gz: bce40d5abece6bcb5be025ac195e12120112ee79
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dca3d06438a1bd3678f0ff2aedad71abf4b6fed262231c772cb1c431f29260e84133ec4433aa0f86d8a05141143b54f440d585380e6a43505a5351907f0f733c
|
7
|
+
data.tar.gz: 5109060c35dd4d591a4ed1bb575305687b35f71bda09d1da40f03ab1284235f6f4a853460fedb6a8de5489e9ad3f44342d2bfa0a2551c6d30f3f02ade3b5bc2e
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
### 0.1.0
|
4
|
+
_August 14th, 2017_
|
5
|
+
|
6
|
+
- ruby gem
|
7
|
+
- properly implemented weekly report
|
8
|
+
- optional app name parameter
|
9
|
+
- post to slack feature (replaces format)
|
10
|
+
- code structure improvement
|
11
|
+
- CI via Travis
|
12
|
+
- changelog, readme, minitest, gemfile
|
13
|
+
|
14
|
+
### 0.1.0-beta.1
|
15
|
+
_August 7th, 2017_
|
16
|
+
|
17
|
+
- minimal working release (deploy report)
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2017 Daniel Sager
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
# appsignal_report
|
2
|
+
|
3
|
+
`appsignal_report` is a gem that generates reports based on data obtained from
|
4
|
+
[Appsignal](https://www.appsignal.com), a tool for monitoring of Ruby and
|
5
|
+
Elixir applications.
|
6
|
+
|
7
|
+
## Status
|
8
|
+
|
9
|
+
[![Build Status](https://travis-ci.org/dsager/appsignal_report.svg?branch=master)](https://travis-ci.org/dsager/appsignal_report)
|
10
|
+
|
11
|
+
## Reports
|
12
|
+
|
13
|
+
Currently the gem supports two kinds of reports, a deploy report and a weekly
|
14
|
+
report.
|
15
|
+
|
16
|
+
## Deploy Report
|
17
|
+
|
18
|
+
The deploy report pulls metrics for the time around the last deploy recorded on
|
19
|
+
AppSignal (1 hour before to 1 hour after). Based on these metrics it calculates
|
20
|
+
changes in response time, error rate and throughput, possibly caused by the
|
21
|
+
deploy. The 20 minutes around the deploy are ignored to account for timeouts or
|
22
|
+
slow requests caused by service restarts or cold caches.
|
23
|
+
|
24
|
+
This only works if you are using AppSignal's
|
25
|
+
[deploy markers](https://docs.appsignal.com/push-api/deploy-marker.html) to
|
26
|
+
record your deployments.
|
27
|
+
|
28
|
+
### Usage
|
29
|
+
|
30
|
+
```
|
31
|
+
$ gem install appsignal_report
|
32
|
+
$ APPSIGNAL_API_TOKEN=ABC123 appsignal_report_deploy -i XYZ456
|
33
|
+
{
|
34
|
+
"title": "AppSignal Deploy Report",
|
35
|
+
"last_deploy_time": "2017-08-10 09:47:52 UTC",
|
36
|
+
"before": {
|
37
|
+
"data_points": 62,
|
38
|
+
"error_rate": 0.4675038958657989,
|
39
|
+
"response_time": 94.9387856438841,
|
40
|
+
"hourly_throughput": 32727
|
41
|
+
},
|
42
|
+
"after": {
|
43
|
+
"data_points": 62,
|
44
|
+
"error_rate": 0.0733371596199222,
|
45
|
+
"response_time": 56.71109484008057,
|
46
|
+
"hourly_throughput": 31362
|
47
|
+
},
|
48
|
+
"data_samples_from": "2017-08-10 08:47:00 UTC",
|
49
|
+
"data_samples_to": "2017-08-10 10:48:00 UTC",
|
50
|
+
"diff": {
|
51
|
+
"error_rate": -0.39416673624587667,
|
52
|
+
"error_rate_pct": -0.8431303775894644,
|
53
|
+
"response_time": -38.22769080380353,
|
54
|
+
"response_time_pct": -0.40265620151489834,
|
55
|
+
"hourly_throughput": -1365,
|
56
|
+
"hourly_throughput_pct": -0.041708680905674214
|
57
|
+
},
|
58
|
+
"messages": {
|
59
|
+
"info": "The deploy finished at 2017-08-10 09:47:52 UTC",
|
60
|
+
"error_rate": "The error rate decreased by 0.39% (from 0.47% to 0.07%, that is a change of -84.31%).",
|
61
|
+
"response_time": "The response time decreased by 38.23ms (from 94.94ms to 56.71ms, that is a change of -40.27%).",
|
62
|
+
"hourly_throughput": "The hourly throughput decreased by 1365.0 req/h (from 32727.0 req/h to 31362.0 req/h, that is a change of -4.17%)."
|
63
|
+
}
|
64
|
+
}
|
65
|
+
```
|
66
|
+
|
67
|
+
## Weekly Report
|
68
|
+
|
69
|
+
The weekly report pulls metrics for the last two weeks. Based on these metrics
|
70
|
+
it calculates changes in response time, error rate and throughput, comparing one
|
71
|
+
week to the other.
|
72
|
+
|
73
|
+
### Usage
|
74
|
+
|
75
|
+
```
|
76
|
+
$ gem install appsignal_report
|
77
|
+
$ APPSIGNAL_API_TOKEN=ABC123 appsignal_report_weekly -i XYZ456
|
78
|
+
{
|
79
|
+
"title": "AppSignal Weekly Report",
|
80
|
+
"now": "2017-08-14 13:19:15 UTC",
|
81
|
+
"one_week_ago": "2017-08-07 13:19:15 UTC",
|
82
|
+
"two_weeks_ago": "2017-07-31 13:19:15 UTC",
|
83
|
+
"before": {
|
84
|
+
"data_points": 168,
|
85
|
+
"error_rate": 0.19637939102939866,
|
86
|
+
"response_time": 74.6427894180237,
|
87
|
+
"hourly_throughput": 34611.166666666664
|
88
|
+
},
|
89
|
+
"after": {
|
90
|
+
"data_points": 168,
|
91
|
+
"error_rate": 0.06227341986188213,
|
92
|
+
"response_time": 61.364840452820985,
|
93
|
+
"hourly_throughput": 30819.684523809523
|
94
|
+
},
|
95
|
+
"data_samples_from": "2017-07-31 14:00:00 UTC",
|
96
|
+
"data_samples_to": "2017-08-14 13:00:00 UTC",
|
97
|
+
"diff": {
|
98
|
+
"error_rate": -0.13410597116751655,
|
99
|
+
"error_rate_pct": -0.6828922855119783,
|
100
|
+
"response_time": -13.277948965202711,
|
101
|
+
"response_time_pct": -0.17788655901967856,
|
102
|
+
"hourly_throughput": -3791.4821428571413,
|
103
|
+
"hourly_throughput_pct": -0.1095450546169726
|
104
|
+
},
|
105
|
+
"messages": {
|
106
|
+
"info": "Comparing the weeks 2017-07-31-2017-08-07 and 2017-08-07-2017-08-14.",
|
107
|
+
"error_rate": "The error rate decreased by 0.13% (from 0.2% to 0.06%, that is a change of -68.29%).",
|
108
|
+
"response_time": "The response time decreased by 13.28ms (from 74.64ms to 61.36ms, that is a change of -17.79%).",
|
109
|
+
"hourly_throughput": "The hourly throughput decreased by 3791.48 req/h (from 34611.17 req/h to 30819.68 req/h, that is a change of -10.95%)."
|
110
|
+
}
|
111
|
+
}
|
112
|
+
```
|
113
|
+
|
114
|
+
## FAQ
|
115
|
+
|
116
|
+
### Where do I get an AppSignal API Token?
|
117
|
+
|
118
|
+
Every AppSignal user automatically has an API token, which is displayed at the
|
119
|
+
bottom of [your personal settings page](https://appsignal.com/users/edit).
|
120
|
+
|
121
|
+
### Where do I find the AppSignal application ID?
|
122
|
+
|
123
|
+
The application ID is part of any appsignal.com URL, as soon as you open an
|
124
|
+
application. If you look at the example URL
|
125
|
+
`appsignal.com/devex/sites/XYZ456/web/exceptions`, the application ID is
|
126
|
+
`XYZ456`.
|
127
|
+
|
128
|
+
## Maintainer
|
129
|
+
|
130
|
+
[Daniel Sager](https://github.com/dsager)
|
131
|
+
|
132
|
+
## Contributing
|
133
|
+
|
134
|
+
- Fork this repository
|
135
|
+
- Implement your feature or fix including Tests
|
136
|
+
- Update the [change log](CHANGELOG.md)
|
137
|
+
- Commit your changes with a meaningful commit message
|
138
|
+
- Create a pull request
|
139
|
+
|
140
|
+
Thank you!
|
141
|
+
|
142
|
+
See the
|
143
|
+
[list of contributors](https://github.com/dsager/appsignal-report/contributors).
|
144
|
+
|
145
|
+
### Tests
|
146
|
+
|
147
|
+
For the whole test suite, run `rake test`.
|
148
|
+
For individual tests, run
|
149
|
+
`ruby -Ilib:spec spec/appsignal_report/version_spec.rb`.
|
150
|
+
|
151
|
+
## License
|
152
|
+
|
153
|
+
MIT License, see the [license file](LICENSE).
|
@@ -0,0 +1,72 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
4
|
+
#
|
5
|
+
# Script to generate a report based on the last Appsignal deploy marker.
|
6
|
+
#
|
7
|
+
# The script will obtain the time of the last deploy from AppSignal and then
|
8
|
+
# pull metrics for the time around that deploy (1 hour before to 1 hour after).
|
9
|
+
# Based on these metrics it will calculate changes in response time, error rate
|
10
|
+
# and throughput, possibly caused by the deploy.
|
11
|
+
# The 20 minutes around the deploy are ignored to account for errors or slow
|
12
|
+
# requests caused by service restarts or cold caches.
|
13
|
+
#
|
14
|
+
# Pull up the help message to learn about the usage of this script:
|
15
|
+
#
|
16
|
+
# ./bin/appsignal_report_deploy --help
|
17
|
+
#
|
18
|
+
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
19
|
+
|
20
|
+
require 'appsignal_report'
|
21
|
+
require 'optparse'
|
22
|
+
|
23
|
+
options = { slack_webhook: nil, app_id: nil, app_name: nil }
|
24
|
+
parser = OptionParser.new do |parser|
|
25
|
+
parser.banner =
|
26
|
+
'Usage: APPSIGNAL_API_TOKEN=XXX ./bin/appsignal_report_deploy [options]'
|
27
|
+
parser.separator ''
|
28
|
+
parser.separator 'Specific options:'
|
29
|
+
parser.on('-i ID',
|
30
|
+
'--app-id ID',
|
31
|
+
String,
|
32
|
+
'Specify Appsignal App Id') do |id|
|
33
|
+
options[:app_id] = id
|
34
|
+
end
|
35
|
+
parser.on('-n NAME',
|
36
|
+
'--app-name NAME',
|
37
|
+
String,
|
38
|
+
'Specify a name for the Appsignal App') do |name|
|
39
|
+
options[:app_name] = name
|
40
|
+
end
|
41
|
+
parser.on('-s WEBHOOK_URL',
|
42
|
+
'--slack WEBHOOK_URL',
|
43
|
+
String,
|
44
|
+
'Post the report to a Slack Webhook') do |url|
|
45
|
+
options[:slack_webhook] = url
|
46
|
+
end
|
47
|
+
parser.separator ''
|
48
|
+
parser.separator 'Common options:'
|
49
|
+
parser.on_tail('-h', '--help', 'Show this message') do
|
50
|
+
puts parser
|
51
|
+
exit
|
52
|
+
end
|
53
|
+
end
|
54
|
+
parser.parse!
|
55
|
+
|
56
|
+
report = AppsignalReport::DeployReport.new(
|
57
|
+
api_token: ENV['APPSIGNAL_API_TOKEN'],
|
58
|
+
app_id: options[:app_id],
|
59
|
+
app_name: options[:app_name]
|
60
|
+
)
|
61
|
+
|
62
|
+
report.generate
|
63
|
+
|
64
|
+
unless options[:slack_webhook].nil?
|
65
|
+
message = AppsignalReport::SlackMessage.new(
|
66
|
+
report: report,
|
67
|
+
webhook_url: options[:slack_webhook]
|
68
|
+
)
|
69
|
+
puts message.post
|
70
|
+
else
|
71
|
+
puts report.report.to_json
|
72
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
4
|
+
#
|
5
|
+
# Script to generate a report based on Appsignal data over the last two weeks.
|
6
|
+
#
|
7
|
+
# The script will pull metrics for the last two weeks.
|
8
|
+
# Based on these metrics it will calculate changes in response time, error rate
|
9
|
+
# and throughput, comparing one week to the other.
|
10
|
+
#
|
11
|
+
# Pull up the help message to learn about the usage of this script:
|
12
|
+
#
|
13
|
+
# ./bin/appsignal_report_weekly --help
|
14
|
+
#
|
15
|
+
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
16
|
+
|
17
|
+
require 'appsignal_report'
|
18
|
+
require 'optparse'
|
19
|
+
|
20
|
+
options = { slack_webhook: nil, app_id: nil, app_name: nil }
|
21
|
+
parser = OptionParser.new do |parser|
|
22
|
+
parser.banner =
|
23
|
+
'Usage: APPSIGNAL_API_TOKEN=XXX ./bin/appsignal_report_weekly [options]'
|
24
|
+
parser.separator ''
|
25
|
+
parser.separator 'Specific options:'
|
26
|
+
parser.on('-i ID',
|
27
|
+
'--app-id ID',
|
28
|
+
String,
|
29
|
+
'Specify Appsignal App Id') do |id|
|
30
|
+
options[:app_id] = id
|
31
|
+
end
|
32
|
+
parser.on('-n NAME',
|
33
|
+
'--app-name NAME',
|
34
|
+
String,
|
35
|
+
'Specify a name for the Appsignal App') do |name|
|
36
|
+
options[:app_name] = name
|
37
|
+
end
|
38
|
+
parser.on('-s WEBHOOK_URL',
|
39
|
+
'--slack WEBHOOK_URL',
|
40
|
+
String,
|
41
|
+
'Post the report to a Slack Webhook') do |url|
|
42
|
+
options[:slack_webhook] = url
|
43
|
+
end
|
44
|
+
parser.separator ''
|
45
|
+
parser.separator 'Common options:'
|
46
|
+
parser.on_tail('-h', '--help', 'Show this message') do
|
47
|
+
puts parser
|
48
|
+
exit
|
49
|
+
end
|
50
|
+
end
|
51
|
+
parser.parse!
|
52
|
+
|
53
|
+
report = AppsignalReport::WeeklyReport.new(
|
54
|
+
api_token: ENV['APPSIGNAL_API_TOKEN'],
|
55
|
+
app_id: options[:app_id],
|
56
|
+
app_name: options[:app_name]
|
57
|
+
)
|
58
|
+
|
59
|
+
report.generate
|
60
|
+
|
61
|
+
unless options[:slack_webhook].nil?
|
62
|
+
message = AppsignalReport::SlackMessage.new(
|
63
|
+
report: report,
|
64
|
+
webhook_url: options[:slack_webhook]
|
65
|
+
)
|
66
|
+
puts message.post
|
67
|
+
else
|
68
|
+
puts report.report.to_json
|
69
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
module AppsignalReport
|
2
|
+
#
|
3
|
+
# Report base class, defines the general flow and helper methods used by the
|
4
|
+
# specific report classes.
|
5
|
+
#
|
6
|
+
class BaseReport
|
7
|
+
attr_reader :api_token, :app_id, :app_name, :report
|
8
|
+
|
9
|
+
# @param [String] api_token API token, find it here:
|
10
|
+
# <https://appsignal.com/users/edit>
|
11
|
+
# @param [String] app_id Application ID, visible in the URL when your
|
12
|
+
# application is opened on Appsignal.com
|
13
|
+
# @param [String] app_name Application Name, used for the report title
|
14
|
+
def initialize(api_token:, app_id:, app_name: nil)
|
15
|
+
@api_token = api_token
|
16
|
+
@app_id = app_id
|
17
|
+
@app_name = app_name
|
18
|
+
@report = {}
|
19
|
+
end
|
20
|
+
|
21
|
+
# To be defined by subclass, should set the instance var @report.
|
22
|
+
# @return [Hash]
|
23
|
+
def generate
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [String]
|
28
|
+
def title
|
29
|
+
[
|
30
|
+
'AppSignal',
|
31
|
+
self.class.name.split('::').last.split(/(?=[A-Z])/).join(' '),
|
32
|
+
!app_name.nil? ? "(#{app_name})" : nil
|
33
|
+
].compact.join(' ')
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def process_metrics
|
39
|
+
api_response = perform_api_request(metrics_uri)
|
40
|
+
data = balance_samples(gather_samples(api_response[:data]))
|
41
|
+
%i(before after).each do |key|
|
42
|
+
@report[key] = {
|
43
|
+
data_points: data[key].size,
|
44
|
+
error_rate: get_average(data[key], :ex_rate),
|
45
|
+
response_time: get_average(data[key], :mean),
|
46
|
+
hourly_throughput: get_average(data[key], :count),
|
47
|
+
}
|
48
|
+
end
|
49
|
+
@report.merge!(
|
50
|
+
data_samples_from: Time.at(data[:before].first[:timestamp]).utc,
|
51
|
+
data_samples_to: Time.at(data[:after].last[:timestamp]).utc,
|
52
|
+
diff: generate_diff,
|
53
|
+
)
|
54
|
+
@report[:messages] = generate_messages
|
55
|
+
report
|
56
|
+
end
|
57
|
+
|
58
|
+
def generate_messages
|
59
|
+
{
|
60
|
+
info: info_message,
|
61
|
+
error_rate: metric_message(:error_rate, '%'),
|
62
|
+
response_time: metric_message(:response_time, 'ms'),
|
63
|
+
hourly_throughput: metric_message(:hourly_throughput, ' req/h'),
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
def info_message; end
|
68
|
+
|
69
|
+
def metric_message(field, unit = '')
|
70
|
+
<<-txt.split.join(' ')
|
71
|
+
The #{field.to_s.sub('_', ' ')}
|
72
|
+
#{report[:diff][field].positive? ? 'increased' : 'decreased'}
|
73
|
+
by #{report[:diff][field].abs.round(2)}#{unit}
|
74
|
+
(from #{report[:before][field].round(2)}#{unit}
|
75
|
+
to #{report[:after][field].round(2)}#{unit}, that is a change of
|
76
|
+
#{(report[:diff][:"#{field}_pct"] * 100).round(2)}%).
|
77
|
+
txt
|
78
|
+
end
|
79
|
+
|
80
|
+
# @param [Array[Hash]] data
|
81
|
+
# @param [Symbol] field
|
82
|
+
# @return [Float]
|
83
|
+
def get_average(data, field)
|
84
|
+
values = data.map { |row| row[field] }
|
85
|
+
values.inject(0, :+).fdiv(values.size)
|
86
|
+
end
|
87
|
+
|
88
|
+
def generate_diff
|
89
|
+
{
|
90
|
+
error_rate: abs_diff(:error_rate),
|
91
|
+
error_rate_pct: pct_diff(:error_rate),
|
92
|
+
response_time: abs_diff(:response_time),
|
93
|
+
response_time_pct: pct_diff(:response_time),
|
94
|
+
hourly_throughput: abs_diff(:hourly_throughput),
|
95
|
+
hourly_throughput_pct: pct_diff(:hourly_throughput),
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
def abs_diff(key)
|
100
|
+
report[:after][key] - report[:before][key]
|
101
|
+
end
|
102
|
+
|
103
|
+
def pct_diff(key)
|
104
|
+
abs_diff(key).fdiv(report[:before][key])
|
105
|
+
end
|
106
|
+
|
107
|
+
def gather_samples(samples)
|
108
|
+
split_timestamp = report_split_time.to_time.to_i
|
109
|
+
samples.each_with_object(before: [], after: []) do |row, hash|
|
110
|
+
next if timestamp_in_grace_period?(row[:timestamp])
|
111
|
+
if row[:timestamp] < split_timestamp
|
112
|
+
hash[:before] << row
|
113
|
+
else
|
114
|
+
hash[:after] << row
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def balance_samples(samples)
|
120
|
+
sample_size = [samples[:before].size, samples[:after].size].min
|
121
|
+
samples[:before] = samples[:before].last(sample_size)
|
122
|
+
samples[:after] = samples[:after].first(sample_size)
|
123
|
+
samples
|
124
|
+
end
|
125
|
+
|
126
|
+
def report_split_time
|
127
|
+
raise NotImplementedError
|
128
|
+
end
|
129
|
+
|
130
|
+
def timestamp_in_grace_period?(_)
|
131
|
+
false
|
132
|
+
end
|
133
|
+
|
134
|
+
# @return [String]
|
135
|
+
def base_uri
|
136
|
+
"https://appsignal.com/api/#{app_id}"
|
137
|
+
end
|
138
|
+
|
139
|
+
# @return [URI]
|
140
|
+
def metrics_uri
|
141
|
+
raise NotImplementedError
|
142
|
+
end
|
143
|
+
|
144
|
+
# @return [Hash]
|
145
|
+
def perform_api_request(uri)
|
146
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
147
|
+
http.use_ssl = true
|
148
|
+
|
149
|
+
response = http.request(
|
150
|
+
Net::HTTP::Get.new(uri, 'Content-Type' => 'application/json')
|
151
|
+
)
|
152
|
+
if response.is_a? Net::HTTPSuccess
|
153
|
+
JSON.parse(response.body, symbolize_names: true)
|
154
|
+
else
|
155
|
+
raise StandardError,
|
156
|
+
"[API ERROR] #{response.code} - #{response.message}"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module AppsignalReport
|
2
|
+
#
|
3
|
+
# Deploy Report
|
4
|
+
#
|
5
|
+
# Compare metrics of one hour before and after the last deploy:
|
6
|
+
# - Error Rate
|
7
|
+
# - Response Time
|
8
|
+
# - Throughput
|
9
|
+
#
|
10
|
+
class DeployReport < BaseReport
|
11
|
+
def generate
|
12
|
+
@report = {
|
13
|
+
title: title,
|
14
|
+
last_deploy_time: Time.parse(last_deploy[:created_at]).utc,
|
15
|
+
}
|
16
|
+
process_metrics
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
# @return [String]
|
22
|
+
def info_message
|
23
|
+
"The deploy finished at #{report[:last_deploy_time]}"
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [Time|nil]
|
27
|
+
def report_split_time
|
28
|
+
report[:last_deploy_time]
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Boolean]
|
32
|
+
def timestamp_in_grace_period?(timestamp)
|
33
|
+
grace_period = 10 * 60
|
34
|
+
(timestamp - report[:last_deploy_time].to_time.to_i).abs < grace_period
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [URI]
|
38
|
+
def metrics_uri
|
39
|
+
one_hour = 3600 # seconds
|
40
|
+
query = URI.encode_www_form(
|
41
|
+
token: api_token,
|
42
|
+
from: (report[:last_deploy_time] - one_hour).iso8601,
|
43
|
+
to: (report[:last_deploy_time] + one_hour).iso8601,
|
44
|
+
'fields[]': %i(mean count ex_rate)
|
45
|
+
)
|
46
|
+
URI("#{base_uri}/graphs.json?#{query}")
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [Hash]
|
50
|
+
def last_deploy
|
51
|
+
@last_deploy = perform_api_request(last_deploy_marker_uri)[:markers].first
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [URI]
|
55
|
+
def last_deploy_marker_uri
|
56
|
+
query = URI.encode_www_form(
|
57
|
+
token: api_token,
|
58
|
+
kind: :deploy,
|
59
|
+
limit: 1
|
60
|
+
)
|
61
|
+
URI("#{base_uri}/markers.json?#{query}")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module AppsignalReport
|
2
|
+
class SlackMessage
|
3
|
+
attr_reader :report, :webhook_uri
|
4
|
+
|
5
|
+
def initialize(report:, webhook_url:)
|
6
|
+
@report = report
|
7
|
+
@webhook_uri = URI(webhook_url)
|
8
|
+
end
|
9
|
+
|
10
|
+
# @return [Hash]
|
11
|
+
def post
|
12
|
+
http = Net::HTTP.new(webhook_uri.host, webhook_uri.port)
|
13
|
+
http.use_ssl = true
|
14
|
+
|
15
|
+
post =
|
16
|
+
Net::HTTP::Post.new(webhook_uri, 'Content-Type' => 'application/json')
|
17
|
+
post.body = payload.to_json
|
18
|
+
response = http.request(post)
|
19
|
+
|
20
|
+
unless response.is_a? Net::HTTPSuccess
|
21
|
+
raise StandardError,
|
22
|
+
"[API ERROR] #{response.code} - #{response.message}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [Hash]
|
27
|
+
def payload
|
28
|
+
{
|
29
|
+
text: report.title,
|
30
|
+
attachments: attachment_messages.map { |message| { text: message } },
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
def attachment_messages
|
35
|
+
[
|
36
|
+
":information_source: #{report.report[:messages][:info]}",
|
37
|
+
"#{error_rate_icon} #{report.report[:messages][:error_rate]}",
|
38
|
+
"#{response_time_icon} #{report.report[:messages][:response_time]}",
|
39
|
+
"#{throughput_icon} #{report.report[:messages][:hourly_throughput]}",
|
40
|
+
]
|
41
|
+
end
|
42
|
+
|
43
|
+
def error_rate_icon
|
44
|
+
report.report[:diff][:error_rate].negative? ? ':+1:' : ':-1:'
|
45
|
+
end
|
46
|
+
|
47
|
+
def response_time_icon
|
48
|
+
report.report[:diff][:response_time].negative? ? ':+1:' : ':-1:'
|
49
|
+
end
|
50
|
+
|
51
|
+
def throughput_icon
|
52
|
+
up_down =
|
53
|
+
if report.report[:diff][:hourly_throughput].negative?
|
54
|
+
'downwards'
|
55
|
+
else
|
56
|
+
'upwards'
|
57
|
+
end
|
58
|
+
":chart_with_#{up_down}_trend:"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module AppsignalReport
|
2
|
+
#
|
3
|
+
# Weekly Report
|
4
|
+
#
|
5
|
+
# Compare metrics of the last week with the one before that:
|
6
|
+
# - Error Rate
|
7
|
+
# - Response Time
|
8
|
+
# - Throughput
|
9
|
+
#
|
10
|
+
class WeeklyReport < BaseReport
|
11
|
+
def generate
|
12
|
+
@report = {
|
13
|
+
title: title,
|
14
|
+
now: Time.now.utc,
|
15
|
+
one_week_ago: (Time.now - (3600 * 24 * 7)).utc,
|
16
|
+
two_weeks_ago: (Time.now - (3600 * 24 * 14)).utc,
|
17
|
+
}
|
18
|
+
process_metrics
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# @return [String]
|
24
|
+
def info_message
|
25
|
+
week_1 = "#{report[:two_weeks_ago].to_date}-#{report[:one_week_ago].to_date}"
|
26
|
+
week_2 = "#{report[:one_week_ago].to_date}-#{report[:now].to_date}"
|
27
|
+
"Comparing the weeks #{week_1} and #{week_2}."
|
28
|
+
end
|
29
|
+
|
30
|
+
def report_split_time
|
31
|
+
report[:one_week_ago]
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [URI]
|
35
|
+
def metrics_uri
|
36
|
+
query = URI.encode_www_form(
|
37
|
+
token: api_token,
|
38
|
+
from: report[:two_weeks_ago].iso8601,
|
39
|
+
'fields[]': %i(mean count ex_rate)
|
40
|
+
)
|
41
|
+
URI("#{base_uri}/graphs.json?#{query}")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/http'
|
3
|
+
require 'json'
|
4
|
+
require 'time'
|
5
|
+
|
6
|
+
require_relative 'appsignal_report/version'
|
7
|
+
require_relative 'appsignal_report/base_report'
|
8
|
+
require_relative 'appsignal_report/weekly_report'
|
9
|
+
require_relative 'appsignal_report/deploy_report'
|
10
|
+
require_relative 'appsignal_report/slack_message'
|
metadata
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: appsignal_report
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Daniel Sager
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-08-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: Toolkit to pull some Appsignal metrics and generate reports
|
42
|
+
email: software@sager1.de
|
43
|
+
executables:
|
44
|
+
- appsignal_report_deploy
|
45
|
+
- appsignal_report_weekly
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- CHANGELOG.md
|
50
|
+
- LICENSE
|
51
|
+
- README.md
|
52
|
+
- bin/appsignal_report_deploy
|
53
|
+
- bin/appsignal_report_weekly
|
54
|
+
- lib/appsignal_report.rb
|
55
|
+
- lib/appsignal_report/base_report.rb
|
56
|
+
- lib/appsignal_report/deploy_report.rb
|
57
|
+
- lib/appsignal_report/slack_message.rb
|
58
|
+
- lib/appsignal_report/version.rb
|
59
|
+
- lib/appsignal_report/weekly_report.rb
|
60
|
+
homepage: https://github.com/dsager/appsignal_report
|
61
|
+
licenses:
|
62
|
+
- MIT
|
63
|
+
metadata: {}
|
64
|
+
post_install_message:
|
65
|
+
rdoc_options: []
|
66
|
+
require_paths:
|
67
|
+
- lib
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
requirements: []
|
79
|
+
rubyforge_project:
|
80
|
+
rubygems_version: 2.5.2
|
81
|
+
signing_key:
|
82
|
+
specification_version: 4
|
83
|
+
summary: Useful reports around your AppSignal metrics
|
84
|
+
test_files: []
|