instrumental_tools 1.0.0 → 1.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/BUILD.md +15 -0
  3. data/CHANGELOG.md +17 -1
  4. data/CUSTOM_METRICS.md +4 -0
  5. data/INSTALL.md +12 -7
  6. data/README.md +2 -2
  7. data/Rakefile +202 -64
  8. data/TEST.md +18 -0
  9. data/bin/instrument_server +31 -15
  10. data/chef/.kitchen.yml +10 -1
  11. data/chef/instrumental_tools/attributes/default.rb +29 -2
  12. data/chef/instrumental_tools/recipes/default.rb +188 -17
  13. data/chef/instrumental_tools/templates/default/instrument_server.erb +46 -0
  14. data/chef/instrumental_tools/templates/{instrumental.yml.erb → default/instrumental.yml.erb} +6 -6
  15. data/chef/omnibus.sh +27 -0
  16. data/conf/instrumental.yml +6 -6
  17. data/examples/README.md +1 -0
  18. data/examples/redis/README.md +10 -0
  19. data/examples/redis/redis_info.sh +10 -0
  20. data/ext/Rakefile +1 -0
  21. data/ext/mkrf_conf.rb +18 -0
  22. data/instrumental_tools.gemspec +8 -2
  23. data/lib/instrumental_tools/metric_script_executor.rb +45 -6
  24. data/lib/instrumental_tools/server_controller.rb +4 -1
  25. data/lib/instrumental_tools/system_inspector.rb +3 -0
  26. data/lib/instrumental_tools/system_inspector/win32.rb +85 -0
  27. data/lib/instrumental_tools/version.rb +1 -1
  28. data/test/integration/default/serverspec/instrumental_tools_spec.rb +32 -16
  29. data/win32/Makefile +18 -0
  30. data/win32/installer.nsis.erb +242 -0
  31. data/win32/logo.ico +0 -0
  32. data/win32/src/instrumental/InstrumentServerProcess.cs +147 -0
  33. data/win32/src/instrumental/InstrumentServerProcessWorker.cs +89 -0
  34. data/win32/src/instrumental/InstrumentServerService.cs +146 -0
  35. data/win32/src/instrumental/InstrumentServerServiceInstaller.cs +60 -0
  36. metadata +36 -7
Binary file
@@ -0,0 +1,147 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using System.Diagnostics;
4
+
5
+
6
+
7
+ namespace Instrumental
8
+ {
9
+
10
+ class InstrumentServerProcess
11
+ {
12
+
13
+ private EventLog destination;
14
+ private Process process;
15
+
16
+ public InstrumentServerProcess(EventLog log){
17
+ destination = log;
18
+ }
19
+
20
+ public void Run(string executablePath, string configPath, string hostname, bool scriptsEnabled, string scriptsDirectory){
21
+ SetupProcess(executablePath, configPath, hostname, scriptsEnabled, scriptsDirectory);
22
+ }
23
+
24
+ public bool IsRunning(){
25
+ if(process == null){
26
+ return false;
27
+ }
28
+ try {
29
+ return !Convert.ToBoolean(process?.HasExited);
30
+ } catch(InvalidOperationException){
31
+ return false;
32
+ }
33
+ }
34
+
35
+ public void CleanupProcess(){
36
+ if(IsRunning()){
37
+ process.Kill();
38
+ process.WaitForExit();
39
+ }
40
+ process?.Close();
41
+ process = null;
42
+ }
43
+
44
+ public TimeSpan Age(){
45
+ DateTime lastStarted = process?.StartTime ?? DateTime.Now;
46
+ return DateTime.Now - lastStarted;
47
+ }
48
+
49
+ public string RubyDir(string basePath){
50
+ return basePath + "\\lib\\ruby";
51
+ }
52
+
53
+ public string RubyLibDir(string basePath){
54
+ return RubyDir(basePath) + "\\lib\\ruby";
55
+ }
56
+
57
+ public string AppDir(string basePath){
58
+ return basePath + "\\lib\\app";
59
+ }
60
+
61
+ public string VendorDir(string basePath){
62
+ return basePath + "\\lib\\vendor";
63
+ }
64
+
65
+ public string Gemfile(string basePath){
66
+ return VendorDir(basePath) + "\\Gemfile";
67
+ }
68
+
69
+ public string RubyExecutable(string basePath){
70
+ return RubyDir(basePath) + "\\bin.real\\ruby.exe";
71
+ }
72
+
73
+ public string InstrumentServerScript(string basePath){
74
+ return AppDir(basePath) + "\\bin\\instrument_server";
75
+ }
76
+
77
+ public string RubyFlags(){
78
+ return "-rbundler/setup";
79
+ }
80
+
81
+ public string SslCertFile(string basePath){
82
+ return RubyDir(basePath) + "\\lib\\ca-bundle.crt";
83
+ }
84
+
85
+ public void SetupProcess(string executablePath, string configPath, string hostname, bool scriptsEnabled, string scriptsDirectory){
86
+ CleanupProcess();
87
+ string args = $"{RubyFlags()} \"{InstrumentServerScript(executablePath)}\" -f \"{configPath}\" -H \"{hostname}\"";
88
+ if(scriptsEnabled){
89
+ args += $" -e -s \"{scriptsDirectory}\"";
90
+ }
91
+ args += " foreground";
92
+
93
+ string rubyVersion = "2.1.0";
94
+ string rubyArch = "i386-mingw32";
95
+ string rubyDir = RubyLibDir(executablePath);
96
+ string[] libDirs = new string[]{
97
+ $"{rubyDir}\\site_ruby\\{rubyVersion}",
98
+ $"{rubyDir}\\site_ruby\\{rubyVersion}\\{rubyArch}",
99
+ $"{rubyDir}\\site_ruby",
100
+ $"{rubyDir}\\vendor_ruby\\{rubyVersion}",
101
+ $"{rubyDir}\\vendor_ruby\\{rubyVersion}\\{rubyArch}",
102
+ $"{rubyDir}\\vendor_ruby",
103
+ $"{rubyDir}\\{rubyVersion}",
104
+ $"{rubyDir}\\{rubyVersion}\\{rubyArch}"
105
+ };
106
+
107
+ Dictionary<string, string> env = new Dictionary<string, string>(){
108
+ { "BUNDLE_GEMFILE", Gemfile(executablePath) },
109
+ { "RUBYLIB", String.Join(";", libDirs) },
110
+ { "SSL_CERT_FILE", SslCertFile(executablePath) }
111
+ };
112
+
113
+ destination.WriteEntry($"Trying to start {RubyExecutable(executablePath)} {args} with env {String.Join(";", env)}", EventLogEntryType.Information);
114
+
115
+ process = new Process();
116
+ process.StartInfo.FileName = RubyExecutable(executablePath);
117
+ process.StartInfo.Arguments = args;
118
+
119
+ foreach(KeyValuePair<string, string> entry in env){
120
+ process.StartInfo.EnvironmentVariables[entry.Key] = entry.Value;
121
+ }
122
+
123
+ process.StartInfo.UseShellExecute = false;
124
+ process.StartInfo.RedirectStandardOutput = true;
125
+ process.StartInfo.RedirectStandardError = true;
126
+ process.StartInfo.CreateNoWindow = true;
127
+ process.OutputDataReceived += new DataReceivedEventHandler((sender, e) => {
128
+ if(!String.IsNullOrEmpty(e.Data)){
129
+ destination.WriteEntry(e.Data, EventLogEntryType.Information);
130
+ }
131
+ });
132
+ process.ErrorDataReceived += new DataReceivedEventHandler((sender, e) => {
133
+ if(!String.IsNullOrEmpty(e.Data)){
134
+ destination.WriteEntry(e.Data, EventLogEntryType.Error);
135
+ }
136
+ });
137
+ if(!process.Start()){
138
+ destination.WriteEntry("Failed to start process", EventLogEntryType.Error);
139
+ } else {
140
+ process.BeginOutputReadLine();
141
+ process.BeginErrorReadLine();
142
+ }
143
+ }
144
+
145
+
146
+ }
147
+ }
@@ -0,0 +1,89 @@
1
+ using System;
2
+ using System.Diagnostics;
3
+ using System.Threading;
4
+
5
+ namespace Instrumental
6
+ {
7
+
8
+ class InstrumentServerProcessWorker
9
+ {
10
+
11
+ private volatile bool doRun;
12
+ private string executablePath;
13
+ private string configPath;
14
+ private string hostname;
15
+ private bool scriptsEnabled;
16
+ private string scriptsDirectory;
17
+ private EventLog destination;
18
+ private Thread runnerThread;
19
+ private TimeSpan checkInterval;
20
+ private int retryFallback;
21
+ private int retries;
22
+ private int maxFallbackMult;
23
+
24
+ public InstrumentServerProcessWorker(EventLog log, string execPath, string confPath, string host, bool enableScripts, string scriptDir){
25
+ destination = log;
26
+ executablePath = execPath;
27
+ configPath = confPath;
28
+ hostname = host;
29
+ scriptsEnabled = enableScripts;
30
+ scriptsDirectory = scriptDir;
31
+ runnerThread = null;
32
+ doRun = false;
33
+ checkInterval = TimeSpan.FromSeconds(5);
34
+ retryFallback = 7; // Seconds
35
+ maxFallbackMult = 3; // Seconds
36
+ retries = -1;
37
+ }
38
+
39
+ public void Run(){
40
+ destination.WriteEntry("Starting worker thread", EventLogEntryType.Information);
41
+ InstrumentServerProcess process = new InstrumentServerProcess(destination);
42
+ try {
43
+ process.Run(executablePath, configPath, hostname, scriptsEnabled, scriptsDirectory);
44
+ } catch (Exception ex) {
45
+ destination.WriteEntry("Exception trying to start, " + ex.Message, EventLogEntryType.Error);
46
+ }
47
+ DateTime lastIter = DateTime.Now;
48
+ DateTime lastExec = lastIter;
49
+ while(doRun){
50
+ Thread.Sleep(checkInterval);
51
+ try {
52
+ if(!process.IsRunning()){
53
+ if( (DateTime.Now - lastExec) > TimeSpan.FromSeconds(Math.Pow(retryFallback, retries))){
54
+ destination.WriteEntry("Attempting to start process", EventLogEntryType.Information);
55
+ process.Run(executablePath, configPath, hostname, scriptsEnabled, scriptsDirectory);
56
+ lastExec = DateTime.Now;
57
+ retries = Math.Min(retries + 1, maxFallbackMult);
58
+ }
59
+ }
60
+ } catch (Exception ex) {
61
+ destination.WriteEntry("Exception trying to start, " + ex.Message, EventLogEntryType.Error);
62
+ retries = Math.Min(retries + 1, maxFallbackMult);
63
+ }
64
+ }
65
+ process.CleanupProcess();
66
+ destination.WriteEntry("Worker thread ended", EventLogEntryType.Information);
67
+ }
68
+
69
+ public void RequestStop(){
70
+ doRun = false;
71
+ }
72
+
73
+ public void Stop(){
74
+ RequestStop();
75
+ runnerThread?.Join();
76
+ runnerThread = null;
77
+ }
78
+
79
+ public void Start(){
80
+ if(Convert.ToBoolean(runnerThread?.IsAlive)){
81
+ Stop();
82
+ }
83
+ doRun = true;
84
+ runnerThread = new Thread(this.Run);
85
+ runnerThread.Start();
86
+ }
87
+
88
+ }
89
+ }
@@ -0,0 +1,146 @@
1
+ using Microsoft.Win32;
2
+
3
+ using System;
4
+ using System.Diagnostics;
5
+ using System.IO;
6
+ using System.Reflection;
7
+ using System.ServiceProcess;
8
+ using System.Threading;
9
+
10
+ namespace Instrumental
11
+ {
12
+ class InstrumentServerService : ServiceBase
13
+ {
14
+ public const string NameOfService = "Instrument Server";
15
+ public const string PathKey = "Path";
16
+ public const string ConfigKey = "Config";
17
+ public const string HostnameKey = "Hostname";
18
+ public const string EnableScriptsKey = "ScriptsEnabled";
19
+ public const string ScriptsDirectoryKey = "ScriptsDirectory";
20
+ public const string InstrumentalKey = "Instrumental";
21
+
22
+ private InstrumentServerProcessWorker ProcessWorker;
23
+
24
+ public InstrumentServerService()
25
+ {
26
+ this.ServiceName = InstrumentServerService.NameOfService;
27
+
28
+ this.EventLog.Log = "Application";
29
+
30
+ this.CanHandlePowerEvent = false;
31
+ this.CanHandleSessionChangeEvent = false;
32
+ this.CanPauseAndContinue = false;
33
+ this.CanShutdown = false;
34
+ this.CanStop = true;
35
+
36
+ string path = BasePath() ?? DefaultBasePath();
37
+ EventLog.WriteEntry($"Starting with path {BasePath()} (Default: {DefaultBasePath()}), config {Config()} (Default: {DefaultConfig()}), hostname {Hostname()} (Default: {DefaultHostname()}), scripts enabled {ScriptsEnabled()}, scripts directory {ScriptsDirectory()} (Default: {DefaultScriptsDirectory()}), values taken from {InstrumentalRegistryKey()}", EventLogEntryType.Information);
38
+
39
+ this.ProcessWorker = new InstrumentServerProcessWorker(EventLog,
40
+ path,
41
+ Config() ?? DefaultConfig(),
42
+ Hostname() ?? DefaultHostname(),
43
+ ScriptsEnabled(),
44
+ ScriptsDirectory() ?? DefaultScriptsDirectory());
45
+ }
46
+
47
+ static void Main()
48
+ {
49
+ ServiceBase.Run(new InstrumentServerService());
50
+ }
51
+
52
+ public static RegistryKey InstrumentalRegistryKey(bool withWriteAccess = false){
53
+ RegistryKey localKey;
54
+ if(Environment.Is64BitOperatingSystem){
55
+ localKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
56
+ } else {
57
+ localKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32);
58
+ }
59
+ if(withWriteAccess){
60
+ return localKey.OpenSubKey("SOFTWARE", true).CreateSubKey(InstrumentalKey);
61
+ } else {
62
+ return localKey.OpenSubKey("SOFTWARE").OpenSubKey(InstrumentalKey);
63
+ }
64
+ }
65
+
66
+ public static string AssemblyDirectory()
67
+ {
68
+ string codeBase = Assembly.GetExecutingAssembly().CodeBase;
69
+ UriBuilder uri = new UriBuilder(codeBase);
70
+ string assemblyPath = Uri.UnescapeDataString(uri.Path);
71
+ return Path.GetDirectoryName(assemblyPath);
72
+ }
73
+
74
+ public static string StringFromRegistryKey(string keyName){
75
+ RegistryKey regkey = InstrumentalRegistryKey();
76
+ if(regkey != null){
77
+ return Convert.ToString(regkey.GetValue(keyName));
78
+ } else {
79
+ return null;
80
+ }
81
+ }
82
+
83
+ public static string BasePath() {
84
+ return StringFromRegistryKey(PathKey);
85
+ }
86
+
87
+ public static string DefaultBasePath() {
88
+ return InstrumentServerService.AssemblyDirectory();
89
+ }
90
+
91
+ public static string Config() {
92
+ return StringFromRegistryKey(ConfigKey);
93
+ }
94
+
95
+ public static string DefaultConfig() {
96
+ return DefaultBasePath() + "\\etc\\instrumental.yml";
97
+ }
98
+
99
+ public static string Hostname(){
100
+ return StringFromRegistryKey(HostnameKey);
101
+ }
102
+
103
+ public static string DefaultHostname(){
104
+ return System.Environment.MachineName;
105
+ }
106
+
107
+ public static bool ScriptsEnabled() {
108
+ object value = InstrumentalRegistryKey()?.GetValue(EnableScriptsKey);
109
+ if(value != null){
110
+ return Convert.ToBoolean(value);
111
+ } else {
112
+ return DefaultScriptsEnabled();
113
+ }
114
+ }
115
+
116
+ public static bool DefaultScriptsEnabled(){
117
+ return false;
118
+ }
119
+
120
+ public static string ScriptsDirectory() {
121
+ return StringFromRegistryKey(ScriptsDirectoryKey);
122
+ }
123
+
124
+ public static string DefaultScriptsDirectory(){
125
+ return DefaultBasePath() + "\\Scripts";
126
+ }
127
+
128
+ protected override void Dispose(bool disposing)
129
+ {
130
+ base.Dispose(disposing);
131
+ }
132
+
133
+ protected override void OnStart(string[] args)
134
+ {
135
+ base.OnStart(args);
136
+ ProcessWorker.Start();
137
+ }
138
+
139
+ protected override void OnStop()
140
+ {
141
+ base.OnStop();
142
+ ProcessWorker.Stop();
143
+ }
144
+
145
+ }
146
+ }
@@ -0,0 +1,60 @@
1
+ using Microsoft.Win32;
2
+
3
+ using System;
4
+ using System.Collections;
5
+ using System.ComponentModel;
6
+ using System.Configuration.Install;
7
+ using System.ServiceProcess;
8
+
9
+ namespace Instrumental
10
+ {
11
+ [RunInstaller(true)]
12
+ public class InstrumentServerServiceInstaller : Installer
13
+ {
14
+
15
+ public const string PathArgument = "Path";
16
+ public const string ConfigArgument = "Config";
17
+ public const string HostnameArgument = "Hostname";
18
+ public const string ScriptsDirectoryArgument = "ScriptsDirectory";
19
+ public const string ScriptsEnabledArgument = "ScriptsEnabled";
20
+
21
+ public InstrumentServerServiceInstaller()
22
+ {
23
+ ServiceProcessInstaller serviceProcessInstaller = new ServiceProcessInstaller();
24
+ ServiceInstaller serviceInstaller = new ServiceInstaller();
25
+
26
+ serviceProcessInstaller.Account = ServiceAccount.LocalService;
27
+ serviceProcessInstaller.Username = null;
28
+ serviceProcessInstaller.Password = null;
29
+
30
+ serviceInstaller.DisplayName = InstrumentServerService.NameOfService;
31
+ serviceInstaller.StartType = ServiceStartMode.Automatic;
32
+ serviceInstaller.ServiceName = InstrumentServerService.NameOfService;
33
+
34
+ this.Installers.Add(serviceProcessInstaller);
35
+ this.Installers.Add(serviceInstaller);
36
+ }
37
+
38
+ public override void Install(IDictionary savedState)
39
+ {
40
+ base.Install(savedState);
41
+
42
+ RegistryKey AppKey = InstrumentServerService.InstrumentalRegistryKey(true);
43
+ string Path = Context.Parameters[PathArgument] ?? InstrumentServerService.DefaultBasePath();
44
+ string Config = Context.Parameters[ConfigArgument] ?? InstrumentServerService.DefaultConfig();
45
+ string ScriptDir = Context.Parameters[ScriptsDirectoryArgument] ?? InstrumentServerService.DefaultScriptsDirectory();
46
+ string Hostname = Context.Parameters[HostnameArgument] ?? InstrumentServerService.DefaultHostname();
47
+
48
+ bool EnableScripts = InstrumentServerService.DefaultScriptsEnabled();
49
+ if(Context.Parameters[ScriptsEnabledArgument] != null){
50
+ EnableScripts = Convert.ToBoolean(Context.Parameters[ScriptsEnabledArgument]);
51
+ }
52
+
53
+ AppKey.SetValue(InstrumentServerService.PathKey, Path);
54
+ AppKey.SetValue(InstrumentServerService.ConfigKey, Config);
55
+ AppKey.SetValue(InstrumentServerService.EnableScriptsKey, EnableScripts);
56
+ AppKey.SetValue(InstrumentServerService.ScriptsDirectoryKey, ScriptDir);
57
+ AppKey.SetValue(InstrumentServerService.HostnameKey, Hostname);
58
+ }
59
+ }
60
+ }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: instrumental_tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Expected Behavior
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-21 00:00:00.000000000 Z
11
+ date: 2015-08-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: instrumental_agent
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 0.12.6
19
+ version: 0.13.2
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 0.12.6
26
+ version: 0.13.2
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: pidly
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -178,13 +178,28 @@ dependencies:
178
178
  - - ">="
179
179
  - !ruby/object:Gem::Version
180
180
  version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: winrm-transport
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: '1.0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: '1.0'
181
195
  description: A collection of scripts useful for monitoring servers and services with
182
196
  Instrumental (instrumentalapp.com)
183
197
  email:
184
198
  - support@instrumentalapp.com
185
199
  executables:
186
200
  - instrument_server
187
- extensions: []
201
+ extensions:
202
+ - ext/mkrf_conf.rb
188
203
  extra_rdoc_files: []
189
204
  files:
190
205
  - BUILD.md
@@ -203,7 +218,9 @@ files:
203
218
  - chef/instrumental_tools/attributes/default.rb
204
219
  - chef/instrumental_tools/metadata.rb
205
220
  - chef/instrumental_tools/recipes/default.rb
206
- - chef/instrumental_tools/templates/instrumental.yml.erb
221
+ - chef/instrumental_tools/templates/default/instrument_server.erb
222
+ - chef/instrumental_tools/templates/default/instrumental.yml.erb
223
+ - chef/omnibus.sh
207
224
  - conf/instrumental.yml
208
225
  - debian/after-install.sh
209
226
  - debian/after-remove.sh
@@ -216,6 +233,10 @@ files:
216
233
  - examples/mongo/mongo_3.rb
217
234
  - examples/mysql/README.md
218
235
  - examples/mysql/mysql_status.rb
236
+ - examples/redis/README.md
237
+ - examples/redis/redis_info.sh
238
+ - ext/Rakefile
239
+ - ext/mkrf_conf.rb
219
240
  - instrumental_tools.gemspec
220
241
  - lib/instrumental_tools/capistrano.rb
221
242
  - lib/instrumental_tools/metric_script_executor.rb
@@ -223,6 +244,7 @@ files:
223
244
  - lib/instrumental_tools/system_inspector.rb
224
245
  - lib/instrumental_tools/system_inspector/linux.rb
225
246
  - lib/instrumental_tools/system_inspector/osx.rb
247
+ - lib/instrumental_tools/system_inspector/win32.rb
226
248
  - lib/instrumental_tools/version.rb
227
249
  - puppet/.kitchen.yml
228
250
  - puppet/.librarian/puppet/config
@@ -238,6 +260,13 @@ files:
238
260
  - rpm/instrument_server
239
261
  - systemd/instrument_server.service
240
262
  - test/integration/default/serverspec/instrumental_tools_spec.rb
263
+ - win32/Makefile
264
+ - win32/installer.nsis.erb
265
+ - win32/logo.ico
266
+ - win32/src/instrumental/InstrumentServerProcess.cs
267
+ - win32/src/instrumental/InstrumentServerProcessWorker.cs
268
+ - win32/src/instrumental/InstrumentServerService.cs
269
+ - win32/src/instrumental/InstrumentServerServiceInstaller.cs
241
270
  homepage: http://github.com/expectedbehavior/instrumental_tools
242
271
  licenses:
243
272
  - MIT
@@ -258,7 +287,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
258
287
  version: '0'
259
288
  requirements: []
260
289
  rubyforge_project:
261
- rubygems_version: 2.2.2
290
+ rubygems_version: 2.4.8
262
291
  signing_key:
263
292
  specification_version: 4
264
293
  summary: Command line tools for Instrumental