metrics_monitor 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: '0922d3a0cebe781c0776d1b4b4dc0f011b0ddf1f3b0638de7d39c39df58ad7be'
4
+ data.tar.gz: 9092c2e02b67f4b3e880b5083a07939823040e7dfd36612b3c0ea8691ddca329
5
+ SHA512:
6
+ metadata.gz: 90cf6c65f5f7baa0f22a18d26e73e65f6ad5d0da2627ff8d3b4c2c98838145b1054ddc41bfbb3f390aa5656af961665ceeac6203be1065b9f76738073464e6a4
7
+ data.tar.gz: 4c1f04de0e7f14e8f297f8355ccbd833b5b5bee3b5ecb8579d46bcd4e42e57cb9aead9d5b1ba7d479c78bc64724b6fd2bbebd7f1e4cf2b9daf8c7c0f9dea1ce0
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,6 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.7.1
6
+ before_install: gem install bundler -v 2.1.4
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in metrics_monitor.gemspec
4
+ gemspec
5
+
6
+ gem "rake", "~> 12.0"
7
+ gem "rspec", "~> 3.0"
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 hogelog
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
13
+ all 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
21
+ THE SOFTWARE.
@@ -0,0 +1,40 @@
1
+ # MetricsMonitor
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/metrics_monitor`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'metrics_monitor'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle install
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install metrics_monitor
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/metrics_monitor.
36
+
37
+
38
+ ## License
39
+
40
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,15 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
8
+ task :before_build do
9
+ chdir "visualizer" do
10
+ rm Dir.glob("dist/*")
11
+ sh "yarn", "build"
12
+ end
13
+ end
14
+
15
+ task build: :before_build
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "metrics_monitor"
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__)
@@ -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
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "optparse"
4
+ require "webrick"
5
+
6
+ opts = {}
7
+ parser = OptionParser.new
8
+ parser.on("-p [PORT]", "--port", "Port") {|v| opts[:port] = v }
9
+ parser.on("-b [BIND]", "--bind", "Bind host") {|v| opts[:bind] = v }
10
+ parser.parse(ARGV)
11
+
12
+ port = opts[:port] || ENV["PORT"] || 8080
13
+ bind = opts[:bind] || ENV["BIND"] || "localhost"
14
+
15
+ gem_dir = File.join(File.expand_path("..", __FILE__), "..")
16
+ Dir.chdir(gem_dir) do
17
+ server = WEBrick::HTTPServer.new(DocumentRoot: './visualizer/dist/', BindAddress: bind, Port: port)
18
+ trap("INT"){ server.shutdown }
19
+ server.start
20
+ end
@@ -0,0 +1,42 @@
1
+ require "metrics_monitor/version"
2
+
3
+ require "metrics_monitor/collector_base"
4
+ require "metrics_monitor/basic_collector"
5
+
6
+ require "metrics_monitor/agent"
7
+
8
+ module MetricsMonitor
9
+ class Error < StandardError; end
10
+ class CollectorError < Error; end
11
+
12
+ DEFAULT_BIND = "0.0.0.0"
13
+ DEFAULT_PORT = 8686
14
+
15
+ Config = Struct.new(:bind, :port, :collector, keyword_init: true)
16
+
17
+ class << self
18
+ def configure
19
+ MetricsMonitor.config = Config.new(bind: DEFAULT_BIND, port: DEFAULT_PORT)
20
+ yield(MetricsMonitor.config) if block_given?
21
+ MetricsMonitor.config.collector ||= BasicCollector.new
22
+
23
+ MetricsMonitor.agent = MetricsMonitor::Agent.new
24
+ end
25
+
26
+ def agent=(agent)
27
+ @agent = agent
28
+ end
29
+
30
+ def agent
31
+ @agent
32
+ end
33
+
34
+ def config=(config)
35
+ @config = config
36
+ end
37
+
38
+ def config
39
+ @config
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,52 @@
1
+ require "json"
2
+ require "webrick"
3
+
4
+ module MetricsMonitor
5
+ class Agent
6
+
7
+ HEADER_ALLOW_ORIGIN = "Access-Control-Allow-Origin"
8
+
9
+ def initialize
10
+ @config = MetricsMonitor.config
11
+ @collector = @config.collector
12
+
13
+ @logger = Rails.logger
14
+
15
+ @server = WEBrick::HTTPServer.new({
16
+ BindAddress: @config.bind,
17
+ Port: @config.port,
18
+ })
19
+ @server.mount_proc("/") do |req, res|
20
+ response_text(res, "ok")
21
+ end
22
+ @server.mount_proc("/metrics") do |req, res|
23
+ metrics = @collector.collect
24
+ response_text(res, JSON.generate(metrics))
25
+ end
26
+ @server.mount_proc("/metrics/meta") do |req, res|
27
+ meta = @collector.meta
28
+ response_text(res, JSON.generate(meta))
29
+ end
30
+
31
+ @thread = Thread.new do
32
+ @logger.info "Start MetricsMonitor::Agent #{@config.bind}:#{@config.port}"
33
+ @server.start
34
+ end
35
+
36
+ at_exit do
37
+ @server.shutdown
38
+ if @thread.alive?
39
+ @thread.wakeup
40
+ @thread.join
41
+ end
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def response_text(res, text)
48
+ res.header["Access-Control-Allow-Origin"] = "*"
49
+ res.body = text
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,74 @@
1
+ require "objspace"
2
+ require "open3"
3
+
4
+ module MetricsMonitor
5
+ class BasicCollector < CollectorBase
6
+ PS_PATTERN = /\A\s*(?<pid>\d+)\s+(?<ppid>\d+)\s+(?<cpu>\d+\.\d)\s+(?<mem>\d+\.\d)\s+(?<rss>\d+)\s+(?<vsz>\d+)\s*\z/
7
+ PS_OPTION = "pid,ppid,%cpu,%mem,rss,vsz"
8
+
9
+ def initialize
10
+ @pid = Process.pid
11
+ end
12
+
13
+ def meta
14
+ {
15
+ chart_formats: [
16
+ { key: :process, title: "Process", type: :line },
17
+ { key: :cpu, title: "CPU", type: :area },
18
+ { key: :mem, title: "MEM", type: :area },
19
+ { key: :rss, title: "RSS", type: :area },
20
+ { key: :vsz, title: "VSZ", type: :area },
21
+ ]
22
+ }
23
+ end
24
+
25
+ def calculate
26
+ calculate_ps
27
+ end
28
+
29
+ private
30
+
31
+ def calculate_ps
32
+ pids = [@pid]
33
+ output, error, status = Open3.capture3("ps", "-o", PS_OPTION)
34
+
35
+ if status.success?
36
+ total_cpu = 0.0
37
+ total_mem = 0.0
38
+ total_rss = 0
39
+ total_vsz = 0
40
+
41
+ output.each_line do |line|
42
+ match = PS_PATTERN.match(line)
43
+ next unless match
44
+ pid = match[:pid].to_i
45
+ ppid = match[:ppid].to_i
46
+ cpu = match[:cpu].to_f
47
+ mem = match[:mem].to_f
48
+ rss = match[:rss].to_i
49
+ vsz = match[:vsz].to_i
50
+
51
+ if pids.include?(ppid)
52
+ pids << pid
53
+ elsif pid != @pid
54
+ next
55
+ end
56
+
57
+ total_cpu += cpu
58
+ total_mem += mem
59
+ total_rss += rss
60
+ total_vsz += vsz
61
+ end
62
+ {
63
+ process: pids.size,
64
+ cpu: total_cpu,
65
+ mem: total_mem,
66
+ rss: total_rss,
67
+ vsz: total_vsz,
68
+ }
69
+ else
70
+ raise MetricsMonitor::Error, error
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,13 @@
1
+ module MetricsMonitor
2
+ class CollectorBase
3
+ def collect
4
+ data = calculate
5
+ { ts: Time.now.to_f, data: data }
6
+ rescue => e
7
+ {
8
+ error: e.to_s,
9
+ backtrace: e&.backtrace
10
+ }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ module MetricsMonitor
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,25 @@
1
+ require_relative 'lib/metrics_monitor/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "metrics_monitor"
5
+ spec.version = MetricsMonitor::VERSION
6
+ spec.authors = ["hogelog"]
7
+ spec.email = ["konbu.komuro@gmail.com"]
8
+
9
+ spec.summary = %q{Metrics monitor via HTTP}
10
+ spec.description = spec.summary
11
+ spec.homepage = "https://github.com/hogelog/metrics_monitor"
12
+ spec.license = "MIT"
13
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.4.0")
14
+
15
+ spec.metadata["homepage_uri"] = spec.homepage
16
+ spec.metadata["source_code_uri"] = spec.homepage
17
+
18
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
19
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
20
+ end
21
+ spec.files += Dir.glob(File.join("visualizer", "dist", "*"))
22
+ spec.bindir = "exe"
23
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
+ spec.require_paths = ["lib"]
25
+ end
@@ -0,0 +1,3 @@
1
+ node_modules/
2
+ dist/
3
+ .cache/
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "visualizer",
3
+ "version": "1.0.0",
4
+ "main": "index.js",
5
+ "license": "MIT",
6
+ "private": true,
7
+ "scripts": {
8
+ "build": "NODE_ENV=production parcel build source/index.html --out-dir dist/",
9
+ "serve": "NODE_ENV=development parcel serve source/index.html --out-dir dist/"
10
+ },
11
+ "devDependencies": {
12
+ "@types/node": "^14.0.27",
13
+ "@types/plotly.js": "^1.50.17",
14
+ "@types/react": "^16.9.46",
15
+ "@types/react-dom": "^16.9.8",
16
+ "@types/react-plotly.js": "^2.2.4",
17
+ "parcel-bundler": "^1.12.4",
18
+ "sass": "^1.26.10",
19
+ "typescript": "^3.9.7"
20
+ },
21
+ "dependencies": {
22
+ "@blueprintjs/core": "^3.30.1",
23
+ "@blueprintjs/icons": "^3.20.1",
24
+ "normalize.css": "^8.0.1",
25
+ "plotly.js": "^1.54.7",
26
+ "react": "^16.13.1",
27
+ "react-dom": "^16.13.1",
28
+ "react-plotly.js": "^2.4.0"
29
+ }
30
+ }
@@ -0,0 +1,2 @@
1
+ @import "normalize.css";
2
+ @import "@blueprintjs/core/lib/css/blueprint.css";
@@ -0,0 +1,92 @@
1
+ import * as React from 'react';
2
+ import { useState, useEffect } from 'react';
3
+
4
+ import { Card, Classes } from "@blueprintjs/core";
5
+
6
+ import Plot from 'react-plotly.js';
7
+
8
+ import "./App.scss";
9
+
10
+ const HOST = process.env.NODE_ENV === "development" ? "http://localhost:8080" : "";
11
+ const INTERVAL = 5000;
12
+
13
+ function App(props: { chartFormats: any[]; debug: any; }) {
14
+ const initData: { [key: string]: (number | Date)[] } = { date: [] as Date[] };
15
+ props.chartFormats.forEach((format) => {
16
+ initData[format.key] = [];
17
+ });
18
+ const [intervalId, setIntervalId] = useState(0);
19
+ const [data, setData] = useState(initData);
20
+ const [log, setLog] = useState("");
21
+ const [displayDebug] = useState(props.debug ? "block" : "none");
22
+ const [dataRevision, setDataRevision] = useState(0);
23
+
24
+ useEffect(() => {
25
+ if (intervalId != 0) {
26
+ return;
27
+ }
28
+ let newIntervalId = window.setInterval(()=>{
29
+ fetch("http://localhost:8686/metrics", {
30
+ mode: "cors",
31
+ }).then(res => {
32
+ return res.json();
33
+ }).then((metrics) => {
34
+ data.date.push(new Date(metrics.ts * 1000));
35
+ props.chartFormats.forEach((format) => {
36
+ data[format.key].push( metrics.data[format.key]);
37
+ });
38
+
39
+ setDataRevision(metrics.ts);
40
+ if (props.debug) {
41
+ setLog(JSON.stringify(data));
42
+ }
43
+ });
44
+ }, INTERVAL);
45
+ setIntervalId(newIntervalId);
46
+
47
+ return () => {
48
+ if (intervalId != 0) {
49
+ clearTimeout(intervalId);
50
+ setIntervalId(0);
51
+ }
52
+ };
53
+ });
54
+
55
+ let charts: Plot[] = [];
56
+ props.chartFormats.forEach((format) => {
57
+ charts.push(
58
+ <Plot
59
+ key={ format["key"]},
60
+ data={[{
61
+ x: data["date"],
62
+ y: data[format["key"]],
63
+ type: "scatter",
64
+ mode: "lines+markers",
65
+ fill: 'tozeroy',
66
+ }]}
67
+ layout={ {
68
+ width: 400,
69
+ height: 300,
70
+ title: format["title"],
71
+ yaxis: {
72
+ zeroline: true,
73
+ },
74
+ datarevision: dataRevision,
75
+ } }
76
+ />
77
+ );
78
+ });
79
+
80
+ return (
81
+ <div id="app">
82
+ { charts }
83
+
84
+ <Card style={ {display: displayDebug } }>
85
+ <h3>Debug log</h3>
86
+ <div>{log}</div>
87
+ </Card>
88
+ </div>
89
+ );
90
+ }
91
+
92
+ export default App;