Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b28b801
Switch to psutil and update announce
pglombardo May 31, 2017
9047f7a
Add travis test suite setup.
pglombardo May 31, 2017
9ed7b34
Proper ID generation and conversion
pglombardo Jun 1, 2017
758f287
Add tests to validate ID management
pglombardo Jun 1, 2017
842cad3
Store timer + set daemon flag so procs exit timely
pglombardo Jun 7, 2017
7616d3b
Basic tracer & span tests
pglombardo Jun 8, 2017
32c3ec8
Fix hang, simplify finite state machine events
pglombardo Jun 12, 2017
bfa8827
Linter fixes; Update fsm state check
pglombardo Jun 12, 2017
1530b1c
Include pid in log messages
pglombardo Jun 12, 2017
b19e6d5
Set timer name/daemon for debug; linter improvements
pglombardo Jun 12, 2017
d8595b1
Override start_span so Instana IDs are used via generate_id
pglombardo Jun 12, 2017
479de65
Set test env var
pglombardo Jun 13, 2017
937e02f
Quick fix: delay sending snapshot
pglombardo Jun 13, 2017
2a8d0cf
Proper delta reporting of metrics
pglombardo Jun 13, 2017
c1fc36f
Unite all Data objects under a single namespace
pglombardo Jun 23, 2017
fb45cb5
More tests; use a single tracer
pglombardo Jun 29, 2017
1d36e66
Remove unused imports.
pglombardo Jun 29, 2017
a61125e
If no incoming context, use same id for s & t
pglombardo Jun 29, 2017
261410d
Span buffering; SDK Span type support:
pglombardo Jun 29, 2017
936a600
Better name for unified InstanaSpan module
pglombardo Jun 29, 2017
14f516f
Pylint improvements
pglombardo Jun 29, 2017
1fa6863
Migrate to class style test structure
pglombardo Jun 29, 2017
1986bd1
First draft for a repo README
pglombardo Jun 29, 2017
5a6e165
Rename package to stan; update README
pglombardo Jun 29, 2017
d5bc33f
Expanded span.kind support & tests.
pglombardo Jun 30, 2017
15a5f24
Allow only one sensor per process
pglombardo Jul 3, 2017
440ec9e
Use a dedicated metric collection thread.
pglombardo Jul 3, 2017
396a279
Lowercase what fields we can for now.
pglombardo Jul 3, 2017
d265919
Rename package to instana
pglombardo Jul 3, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
language: python
python:
- "2.7"
- "3.6"
install: "pip install -r requirements.txt"
script: nosetests
38 changes: 37 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,37 @@
# python-sensor
<div align="center">
<img src="https://disznc.s3.amazonaws.com/Python-1-2017-06-29-at-22.34.00.png"/>
</div>

# Instana

The instana package provides Python metrics and traces (request, queue & cross-host) for [Instana](https://www.instana.com/).

## Note

This package supports Python 2.7 or greater.

Any and all feedback is welcome. Happy Python visibility.

## Installation

$ pip install instana

## Usage

The instana package is a zero configuration tool that will automatically collect key metrics from your Python processes. Just install and go.

## Tracing

This Python package supports [OpenTracing](http://opentracing.io/).

## Documentation

You can find more documentation covering supported components and minimum versions in the Instana [documentation portal](https://instana.atlassian.net/wiki/display/DOCS/Python).

## Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/instana/python-sensor.

## More

Want to instrument other languages? See our [Nodejs instrumentation](https://github.com/instana/nodejs-sensor), [Go instrumentation](https://github.com/instana/golang-sensor), [Ruby instrumentation](https://github.com/instana/ruby-sensor) or [many other supported technologies](https://www.instana.com/supported-technologies/).
8 changes: 5 additions & 3 deletions instana/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
except ImportError:
import urllib2


class From(object):
pid = ""
hostId = ""
Expand Down Expand Up @@ -42,10 +43,11 @@ def __init__(self, sensor):
self.reset()

def to_json(self, o):
return json.dumps(o, default=lambda o: o.__dict__, sort_keys=False,separators=(',', ':')).encode()
return json.dumps(o, default=lambda o: o.__dict__,
sort_keys=False, separators=(',', ':')).encode()

def can_send(self):
return self.fsm.fsm.current == "ready"
return self.fsm.fsm.current == "good2go"

def head(self, url):
return self.request(url, "HEAD", None)
Expand Down Expand Up @@ -94,7 +96,7 @@ def full_request_response(self, url, method, o, body, header):
if method == "HEAD":
b = True
except Exception as e:
l.error(str(e))
l.error("full_request_response: " + str(e))

return (b, h)

Expand Down
6 changes: 0 additions & 6 deletions instana/custom.py

This file was deleted.

8 changes: 0 additions & 8 deletions instana/data.py

This file was deleted.

64 changes: 36 additions & 28 deletions instana/fsm.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import subprocess
import os
import sys
import psutil
import threading as t
import fysom as f
import instana.log as l
Expand All @@ -17,35 +17,34 @@ def __init__(self, **kwds):


class Fsm(object):
E_START = "start"
E_LOOKUP = "lookup"
E_ANNOUNCE = "announce"
E_TEST = "test"

RETRY_PERIOD = 30

agent = None
fsm = None
timer = None

def __init__(self, agent):
l.debug("initializing fsm")

self.agent = agent
self.fsm = f.Fysom({
"initial": "none",
"initial": "lostandalone",
"events": [
{"name": self.E_START, "src": [
"none", "unannounced", "announced", "ready"], "dst": "init"},
{"name": self.E_LOOKUP, "src": "init", "dst": "unannounced"},
{"name": self.E_ANNOUNCE, "src": "unannounced", "dst": "announced"},
{"name": self.E_TEST, "src": "announced", "dst": "ready"}],
("startup", "*", "lostandalone"),
("lookup", "lostandalone", "found"),
("announce", "found", "announced"),
("ready", "announced", "good2go")],
"callbacks": {
"onstart": self.lookup_agent_host,
"onenterunannounced": self.announce_sensor,
"onenterannounced": self.test_agent}})
"onlookup": self.lookup_agent_host,
"onannounce": self.announce_sensor,
"onchangestate": self.printstatechange}})

def printstatechange(self, e):
l.debug('========= (%i#%s) FSM event: %s, src: %s, dst: %s ==========' % \
(os.getpid(), t.current_thread().name, e.event, e.src, e.dst))

def reset(self):
self.fsm.start()
self.fsm.lookup()

def lookup_agent_host(self, e):
if self.agent.sensor.options.agent_host != "":
Expand All @@ -56,17 +55,19 @@ def lookup_agent_host(self, e):
h = self.check_host(host)
if h == a.AGENT_HEADER:
self.agent.set_host(host)
self.fsm.lookup()
self.fsm.announce()
else:
host = self.get_default_gateway()
if host:
self.check_host(host)
if h == a.AGENT_HEADER:
self.agent.set_host(host)
self.fsm.lookup()
self.fsm.announce()
else:
l.error("Cannot lookup agent host. Scheduling retry.")
self.schedule_retry(self.lookup_agent_host, e)
self.schedule_retry(self.lookup_agent_host, e, "agent_lookup")
return False
return True

def get_default_gateway(self):
l.debug("checking default gateway")
Expand All @@ -91,29 +92,36 @@ def check_host(self, host):

def announce_sensor(self, e):
l.debug("announcing sensor to the agent")

d = Discovery(pid=os.getpid(),
name=sys.executable,
args=sys.argv[0:])
p = psutil.Process(os.getpid())
d = Discovery(pid=p.pid,
name=p.cmdline()[0],
args=p.cmdline()[1:])

(b, _) = self.agent.request_response(
self.agent.make_url(a.AGENT_DISCOVERY_URL), "PUT", d)
if not b:
l.error("Cannot announce sensor. Scheduling retry.")
self.schedule_retry(self.announce_sensor, e)
self.schedule_retry(self.announce_sensor, e, "announce")
return False
else:
self.agent.set_from(b)
self.fsm.announce()
self.fsm.ready()
return True

def schedule_retry(self, fun, e):
t.Timer(self.RETRY_PERIOD, fun, [e]).start()
def schedule_retry(self, fun, e, name):
l.error("Scheduling: " + name)
self.timer = t.Timer(self.RETRY_PERIOD, fun, [e])
self.timer.daemon = True
self.timer.name = name
self.timer.start()
l.debug('Threadlist: %s', str(t.enumerate()))

def test_agent(self, e):
l.debug("testing communication with the agent")

(b, _) = self.agent.head(self.agent.make_url(a.AGENT_DATA_URL))

if not b:
self.schedule_retry(self.test_agent, e)
self.schedule_retry(self.test_agent, e, "agent test")
else:
self.fsm.test()
11 changes: 0 additions & 11 deletions instana/http.py

This file was deleted.

3 changes: 2 additions & 1 deletion instana/log.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging as l
import os

logger = l.getLogger('instana')
logger = l.getLogger('instana(' + str(os.getpid()) + ')')


def init(level):
Expand Down
48 changes: 36 additions & 12 deletions instana/meter.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import gc as gc_
import sys
import instana.agent_const as a
import copy
import time


class Snapshot(object):
Expand Down Expand Up @@ -63,6 +65,17 @@ class Metrics(object):
def __init__(self, **kwds):
self.__dict__.update(kwds)

def delta_data(self, delta):
data = self.__dict__
if delta is None:
return data

unchanged_items = set(data.items()) & set(delta.items())
for x in unchanged_items:
data.pop(x[0])

return data


class EntityData(object):
pid = 0
Expand All @@ -75,33 +88,44 @@ def __init__(self, **kwds):

class Meter(object):
SNAPSHOT_PERIOD = 600
snapshot_countdown = 1
snapshot_countdown = 30
sensor = None
last_usage = None
last_collect = None
timer = None
last_metrics = None

def __init__(self, sensor):
self.sensor = sensor
self.tick()
self.run()

def run(self):
self.timer = t.Thread(target=self.collect_and_report)
self.timer.daemon = True
self.timer.name = "Instana Metric Collection"
self.timer.start()

def tick(self):
t.Timer(1, self.process).start()
def collect_and_report(self):
while 1:
self.process()
time.sleep(1)

def process(self):
if self.sensor.agent.can_send():
self.snapshot_countdown = self.snapshot_countdown - 1
s = None
cm = self.collect_metrics()
if self.snapshot_countdown == 0:
self.snapshot_countdown = self.SNAPSHOT_PERIOD
s = self.collect_snapshot()

m = self.collect_metrics()
d = EntityData(pid=os.getpid(), snapshot=s, metrics=m)

t.Thread(target=self.sensor.agent.request,
args=(self.sensor.agent.make_url(a.AGENT_DATA_URL), "POST", d)).start()

self.tick()
md = cm.delta_data(None)
else:
md = copy.deepcopy(cm).delta_data(self.last_metrics)

d = EntityData(pid=os.getpid(), snapshot=s, metrics=md)
self.sensor.agent.request(
self.sensor.agent.make_url(a.AGENT_DATA_URL), "POST", d)
self.last_metrics = cm.__dict__

def collect_snapshot(self):
try:
Expand Down
Loading