Skip to content

Commit f2170d7

Browse files
committed
implemented metrics and agent fsm
1 parent 1ddba50 commit f2170d7

File tree

16 files changed

+562
-0
lines changed

16 files changed

+562
-0
lines changed

.gitignore

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# C extensions
7+
*.so
8+
9+
# Distribution / packaging
10+
.Python
11+
env/
12+
build/
13+
develop-eggs/
14+
dist/
15+
downloads/
16+
eggs/
17+
.eggs/
18+
lib/
19+
lib64/
20+
parts/
21+
sdist/
22+
var/
23+
wheels/
24+
*.egg-info/
25+
.installed.cfg
26+
*.egg
27+
28+
# PyInstaller
29+
# Usually these files are written by a python script from a template
30+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
31+
*.manifest
32+
*.spec
33+
34+
# Installer logs
35+
pip-log.txt
36+
pip-delete-this-directory.txt
37+
38+
# Unit test / coverage reports
39+
htmlcov/
40+
.tox/
41+
.coverage
42+
.coverage.*
43+
.cache
44+
nosetests.xml
45+
coverage.xml
46+
*,cover
47+
.hypothesis/
48+
49+
# Translations
50+
*.mo
51+
*.pot
52+
53+
# Django stuff:
54+
*.log
55+
local_settings.py
56+
57+
# Flask stuff:
58+
instance/
59+
.webassets-cache
60+
61+
# Scrapy stuff:
62+
.scrapy
63+
64+
# Sphinx documentation
65+
docs/_build/
66+
67+
# PyBuilder
68+
target/
69+
70+
# Jupyter Notebook
71+
.ipynb_checkpoints
72+
73+
# pyenv
74+
.python-version
75+
76+
# celery beat schedule file
77+
celerybeat-schedule
78+
79+
# dotenv
80+
.env
81+
82+
# virtualenv
83+
.venv/
84+
venv/
85+
ENV/
86+
87+
# Spyder project settings
88+
.spyderproject
89+
90+
# Rope project settings
91+
.ropeproject
92+

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2016 Instana
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.

MANIFEST.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
include LICENSE
2+
include README.md
3+
include requirements.txt

example/simple.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import sys
2+
import instana.sensor as s
3+
import instana.options as o
4+
import logging as l
5+
6+
def main(argv):
7+
sensor = s.Sensor(o.Options(service='python-simple',
8+
log_level=l.DEBUG))
9+
10+
if __name__ == "__main__":
11+
main(sys.argv)

instana/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"""
2+
Instana sensor and tracer. It consists of two modules that can be used as entry points:
3+
4+
- sensor: activates the meter to collect and transmit all kind of built-in metrics
5+
- tracer: OpenTracing tracer implementation. It implicitly activates the meter
6+
"""
7+
8+
__author__ = 'Instana Inc.'
9+
__copyright__ = 'Copyright 2016 Instana Inc.'
10+
__credits__ = ['Pavlo Baron']
11+
__license__ = 'MIT'
12+
__version__ = '0.0.1'
13+
__maintainer__ = 'Pavlo Baron'
14+
__email__ = '[email protected]'
15+
16+
__all__ = ['sensor', 'tracer']

instana/agent.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import json
2+
import thread
3+
import urllib2
4+
import instana.log as l
5+
import instana.fsm as f
6+
7+
class From(object):
8+
pid = ""
9+
hostId = ""
10+
def __init__(self, **kwds):
11+
self.__dict__.update(kwds)
12+
13+
class Head(urllib2.Request):
14+
def get_method(self):
15+
return "HEAD"
16+
17+
class Put(urllib2.Request):
18+
def get_method(self):
19+
return "PUT"
20+
21+
class Agent(object):
22+
AGENT_DISCOVERY_URL = "/com.instana.plugin.python.discovery"
23+
AGENT_TRACES_URL = "/com.instana.plugin.python/traces."
24+
AGENT_DATA_URL = "/com.instana.plugin.python."
25+
AGENT_DEFAULT_HOST = "localhost"
26+
AGENT_DEFAULT_PORT = 42699
27+
AGENT_HEADER = "Instana Agent"
28+
29+
sensor = None
30+
host = AGENT_DEFAULT_HOST
31+
fsm = None
32+
from_ = None
33+
34+
def __init__(self, sensor):
35+
l.debug("initializing agent")
36+
37+
self.sensor = sensor
38+
self.fsm = f.Fsm(self)
39+
self.reset()
40+
41+
def to_json(self, o):
42+
return json.dumps(o, default=lambda o: o.__dict__, sort_keys=False, indent=4)
43+
44+
def can_send(self):
45+
return self.fsm.fsm.current == "ready"
46+
47+
def head(self, url):
48+
return self.request(url, "HEAD", None)
49+
50+
def request(self, url, method, o):
51+
return self.full_request_response(url, method, o, False, "")
52+
53+
def request_response(self, url, method, o):
54+
return self.full_request_response(url, method, o, True, "")
55+
56+
def request_header(self, url, method, header):
57+
return self.full_request_response(url, method, None, False, header)
58+
59+
def full_request_response(self, url, method, o, body, header):
60+
b = None
61+
h = None
62+
try:
63+
if method == "HEAD":
64+
request = Head(url)
65+
elif method == "GET":
66+
request = urllib2.Request(url)
67+
elif method == "PUT":
68+
request = Put(url, self.to_json(o))
69+
request.add_header("Content-Type", "application/json")
70+
else:
71+
request = urllib2.Request(url, self.to_json(o))
72+
request.add_header("Content-Type", "application/json")
73+
74+
response = urllib2.urlopen(request, timeout=2)
75+
76+
if not response:
77+
self.reset()
78+
else:
79+
if response.getcode() < 200 or response.getcode() >= 300:
80+
l.error("Request returned erroneous code", response.getcode())
81+
if self.can_send():
82+
self.reset()
83+
else:
84+
if body:
85+
b = response.read()
86+
87+
if header:
88+
h = response.info().getheader(header)
89+
90+
if method == "HEAD":
91+
b = True
92+
except Exception as e:
93+
l.error(str(e))
94+
95+
return (b, h)
96+
97+
def make_url(self, prefix):
98+
return self.make_host_url(self.host, prefix)
99+
100+
def make_host_url(self, host, prefix):
101+
port = self.sensor.options.agent_port
102+
if port == 0:
103+
port = self.AGENT_DEFAULT_PORT
104+
105+
return self.make_full_url(host, port, prefix)
106+
107+
def make_full_url(self, host, port, prefix):
108+
s = "http://%s:%s%s" % (host, str(port), prefix)
109+
if self.from_.pid != 0:
110+
s = "%s%s" % (s, self.from_.pid)
111+
112+
return s
113+
114+
def reset(self):
115+
self.from_ = From()
116+
self.fsm.reset()
117+
118+
def set_host(self, host):
119+
self.host = host
120+
121+
def set_from(self, from_):
122+
self.from_ = From(**json.loads(from_))

instana/data.py

Whitespace-only changes.

instana/fsm.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import subprocess
2+
import os
3+
import sys
4+
import threading as t
5+
import fysom as f
6+
import instana.log as l
7+
8+
class Discovery(object):
9+
pid = 0
10+
name = None
11+
args = None
12+
def __init__(self, **kwds):
13+
self.__dict__.update(kwds)
14+
15+
class Fsm(object):
16+
E_START = "start"
17+
E_LOOKUP = "lookup"
18+
E_ANNOUNCE = "announce"
19+
E_TEST = "test"
20+
21+
RETRY_PERIOD = 5#30
22+
23+
agent = None
24+
fsm = None
25+
26+
def __init__(self, agent):
27+
l.debug("initializing fsm")
28+
29+
self.agent = agent
30+
self.fsm = f.Fysom({
31+
"initial": "none",
32+
"events": [
33+
{"name": self.E_START, "src": ["none", "unannounced", "announced", "ready"], "dst": "init"},
34+
{"name": self.E_LOOKUP, "src": "init", "dst": "unannounced"},
35+
{"name": self.E_ANNOUNCE, "src": "unannounced", "dst": "announced"},
36+
{"name": self.E_TEST, "src": "announced", "dst": "ready"}],
37+
"callbacks": {
38+
"onstart": self.lookup_agent_host,
39+
"onenterunannounced": self.announce_sensor,
40+
"onenterannounced": self.test_agent}})
41+
42+
def reset(self):
43+
self.fsm.start()
44+
45+
def lookup_agent_host(self, e):
46+
if self.agent.sensor.options.agent_host != "":
47+
host = self.agent.sensor.options.agent_host
48+
else:
49+
host = self.agent.AGENT_DEFAULT_HOST
50+
51+
h = self.check_host(host)
52+
if h == self.agent.AGENT_HEADER:
53+
self.agent.set_host(host)
54+
self.fsm.lookup()
55+
else:
56+
self.check_host(self.get_default_gateway())
57+
if h == self.agent.AGENT_HEADER:
58+
self.agent.set_host(host)
59+
self.fsm.lookup()
60+
61+
def get_default_gateway(self):
62+
l.debug("checking default gateway")
63+
proc = subprocess.Popen("/sbin/ip route | awk '/default/ { print $3 }'", stdout=subprocess.PIPE)
64+
65+
return proc.stdout.read()
66+
67+
def check_host(self, host):
68+
l.debug("checking host", host)
69+
70+
(b, h) = self.agent.request_header(self.agent.make_host_url(host, "/"), "GET", "Server")
71+
72+
return h
73+
74+
def announce_sensor(self, e):
75+
l.debug("announcing sensor to the agent")
76+
77+
d = Discovery(pid=os.getpid(),
78+
name=sys.executable,
79+
args=sys.argv[0:])
80+
81+
(b, h) = self.agent.request_response(self.agent.make_url(self.agent.AGENT_DISCOVERY_URL), "PUT", d)
82+
if not b:
83+
l.error("Cannot announce sensor. Scheduling retry.")
84+
self.schedule_retry(self.announce_sensor, e)
85+
else:
86+
self.agent.set_from(b)
87+
self.fsm.announce()
88+
89+
def schedule_retry(self, f, e):
90+
t.Timer(self.RETRY_PERIOD, f, [e]).start()
91+
92+
def test_agent(self, e):
93+
l.debug("testing communication with the agent")
94+
95+
(b, h) = self.agent.head(self.agent.make_url(self.agent.AGENT_DATA_URL))
96+
97+
if not b:
98+
self.schedule_retry(self.test_agent, e)
99+
else:
100+
self.fsm.test()

instana/log.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import logging as l
2+
3+
logger = l.getLogger('instana')
4+
5+
def init(level):
6+
ch = l.StreamHandler()
7+
f = l.Formatter('%(asctime)s: %(levelname)s: %(name)s: %(message)s')
8+
ch.setFormatter(f)
9+
logger.addHandler(ch)
10+
logger.setLevel(level)
11+
12+
def debug(s, *args):
13+
logger.debug("%s %s" % (s, ' '.join(args)))
14+
15+
def info(s, *args):
16+
logger.info("%s %s" % (s, ' '.join(args)))
17+
18+
def warn(s, *args):
19+
logger.warn("%s %s" % (s, ' '.join(args)))
20+
21+
def error(s, *args):
22+
logger.error("%s %s" % (s, ' '.join(args)))

0 commit comments

Comments
 (0)