Skip to content

Commit 0e18d50

Browse files
authored
Merge pull request #278 from getappmap/process-recording_20240205
Add process recording
2 parents 7479959 + 805ae30 commit 0e18d50

23 files changed

+181
-139
lines changed

README.md

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,36 +24,22 @@ of code and key data flows.
2424

2525
Visit the [AppMap for Python](https://appland.com/docs/reference/appmap-python.html) reference page on AppLand.com for a complete reference guide.
2626

27-
# Development
27+
# Development
2828

2929
[![Build Status](https://travis-ci.com/getappmap/appmap-python.svg?branch=master)](https://travis-ci.com/getappmap/appmap-python)
3030

3131
## Getting the code
32-
Clone the repo to begin development. Note that vendored dependencies are included as
33-
submodules.
32+
Clone the repo to begin development.
3433

3534
```shell
36-
% git clone --recurse-submodules https://github.com/applandinc/appmap-python.git
35+
% git clone https://github.com/applandinc/appmap-python.git
3736
Cloning into 'appmap-python'...
3837
remote: Enumerating objects: 167, done.
3938
remote: Counting objects: 100% (167/167), done.
4039
remote: Compressing objects: 100% (100/100), done.
4140
remote: Total 962 (delta 95), reused 116 (delta 61), pack-reused 795
4241
Receiving objects: 100% (962/962), 217.31 KiB | 4.62 MiB/s, done.
4342
Resolving deltas: 100% (653/653), done.
44-
Submodule 'extern/wrapt' (https://github.com/applandinc/wrapt.git) registered for path 'vendor/wrapt'
45-
Cloning into '/private/tmp/appmap-python/vendor/wrapt'...
46-
remote: Enumerating objects: 46, done.
47-
remote: Counting objects: 100% (46/46), done.
48-
remote: Compressing objects: 100% (39/39), done.
49-
remote: Total 2537 (delta 9), reused 19 (delta 4), pack-reused 2491
50-
Receiving objects: 100% (2537/2537), 755.94 KiB | 7.48 MiB/s, done.
51-
Resolving deltas: 100% (1643/1643), done.
52-
Submodule path 'vendor/wrapt': checked out '9bdfbe54b88a64069cba1f3c36e77edc3c1339c9'
53-
54-
% ls appmap-python/vendor/wrapt
55-
LICENSE Makefile appveyor.yml docs src tests
56-
MANIFEST.in README.rst blog setup.py tddium.yml tox.ini
5743
```
5844

5945
## Python version support
@@ -114,7 +100,7 @@ Note that you must install the dependencies contained in
114100

115101
### tox
116102
Additionally, the `tox` configuration provides the ability to run the tests for all
117-
supported versions of Python and djanggo.
103+
supported versions of Python and Django.
118104

119105
`tox` requires that all the correct versions of Python to be available to create
120106
the test environments. [pyenv](https://github.com/pyenv/pyenv) is an easy way to manage

_appmap/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from . import configuration
22
from . import env as appmapenv
3-
from . import event, importer, metadata, recorder, web_framework
3+
from . import event, importer, metadata, recorder, recording, web_framework
44
from .py_version_check import check_py_version
55

66

@@ -13,6 +13,7 @@ def initialize(**kwargs):
1313
configuration.initialize() # needs to be initialized after recorder
1414
metadata.initialize()
1515
web_framework.initialize()
16+
recording.initialize()
1617

1718

1819
initialize()

_appmap/django.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from . import remote_recording
1313

1414

15-
class RemoteRecording: # pylint: disable=missing-class-docstring
15+
class RemoteRecording: # pylint: disable=missing-class-docstring,too-few-public-methods
1616
def __init__(self, get_response):
1717
super().__init__()
1818
self.get_response = get_response

_appmap/event.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ def to_dict(self, attrs=None):
329329
return ret
330330

331331

332-
class SqlEvent(Event):
332+
class SqlEvent(Event): # pylint: disable=too-few-public-methods
333333
__slots__ = ["sql_query"]
334334

335335
def __init__(self, sql, vendor=None, version=None):
@@ -345,7 +345,7 @@ def __init__(self, sql, vendor=None, version=None):
345345
)
346346

347347

348-
class MessageEvent(Event):
348+
class MessageEvent(Event): # pylint: disable=too-few-public-methods
349349
__slots__ = ["message"]
350350

351351
def __init__(self, message_parameters):

_appmap/generation.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def values(self):
1919
return self._dict.values()
2020

2121

22-
class ClassMapEntry:
22+
class ClassMapEntry: # pylint: disable=too-few-public-methods
2323
# pylint: disable=redefined-builtin
2424
def __init__(self, name, type):
2525
self.name = name
@@ -32,19 +32,19 @@ def to_dict(self):
3232
return {k: v for k, v in vars(self).items() if v is not None}
3333

3434

35-
class PackageEntry(ClassMapEntry):
35+
class PackageEntry(ClassMapEntry): # pylint: disable=too-few-public-methods
3636
def __init__(self, name):
3737
super().__init__(name, "package")
3838
self.children = ClassMapDict()
3939

4040

41-
class ClassEntry(ClassMapEntry):
41+
class ClassEntry(ClassMapEntry): # pylint: disable=too-few-public-methods
4242
def __init__(self, name):
4343
super().__init__(name, "class")
4444
self.children = ClassMapDict()
4545

4646

47-
class FuncEntry(ClassMapEntry):
47+
class FuncEntry(ClassMapEntry): # pylint: disable=too-few-public-methods
4848
def __init__(self, e):
4949
super().__init__(e.method_id, "function")
5050
self.location = "%s:%s" % (e.path, e.lineno)

_appmap/importer.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def fntype(self):
6262
return self.scope.classify_fn(self.static_fn)
6363

6464

65-
class Filter(ABC):
65+
class Filter(ABC): # pylint: disable=too-few-public-methods
6666
def __init__(self, next_filter):
6767
self.next_filter = next_filter
6868

@@ -75,7 +75,7 @@ def filter(self, filterable):
7575
"""
7676

7777

78-
class NullFilter(Filter):
78+
class NullFilter(Filter): # pylint: disable=too-few-public-methods
7979
def __init__(self, next_filter=None):
8080
super().__init__(next_filter)
8181

_appmap/labels.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
logger = Env.current.getLogger(__name__)
88

99

10-
class labels:
10+
class labels: # pylint: disable=too-few-public-methods
1111
def __init__(self, *args):
1212
self._labels = args
1313

_appmap/metadata.py

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,10 @@ def base(root_dir):
7272
@lru_cache()
7373
def _git_available(root_dir):
7474
try:
75-
ret = utils.subprocess_run(["git", "status"], cwd=root_dir)
75+
ret = utils.subprocess_run(["git", "rev-parse", "HEAD"], cwd=root_dir)
7676
if not ret.returncode:
7777
return True
78-
logger.warning("Failed running 'git status', %s", ret.stderr)
78+
logger.warning("Failed running 'git rev-parse HEAD', %s", ret.stderr)
7979
except FileNotFoundError as exc:
8080
msg = """
8181
Couldn't find git executable, repository information
@@ -97,34 +97,12 @@ def _git_metadata(root_dir):
9797
repository = git("config --get remote.origin.url")
9898
branch = git("rev-parse --abbrev-ref HEAD")
9999
commit = git("rev-parse HEAD")
100-
status = _lines(git("status -s"))
101-
annotated_tag = git("describe --abbrev=0") or None
102-
tag = git("describe --abbrev=0 --tags") or None
103-
104-
pattern = re.compile(r".*-(\d+)-\w+$")
105-
106-
commits_since_annotated_tag = None
107-
if annotated_tag:
108-
result = pattern.search(git("describe"))
109-
if result:
110-
commits_since_annotated_tag = int(result.group(1))
111-
112-
commits_since_tag = None
113-
if tag:
114-
result = pattern.search(git("describe --tags"))
115-
if result:
116-
commits_since_tag = int(result.group(1))
117100

118101
return compact_dict(
119102
{
120103
"repository": repository,
121104
"branch": branch,
122105
"commit": commit,
123-
"status": status,
124-
"tag": tag,
125-
"annotated_tag": annotated_tag,
126-
"commits_since_tag": commits_since_tag,
127-
"commits_since_annotated_tag": commits_since_annotated_tag,
128106
}
129107
)
130108

_appmap/noappmap.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import sys
2-
31
_appmap_noappmap = "_appmap_noappmap"
42

53

_appmap/recording.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
import atexit
2+
from datetime import datetime, timezone
3+
import os
4+
from tempfile import NamedTemporaryFile
5+
6+
from _appmap import generation
7+
from _appmap.web_framework import APPMAP_SUFFIX, HASH_LEN, NAME_MAX, name_hash
8+
19
from .env import Env
210
from .recorder import Recorder
311

@@ -44,3 +52,50 @@ def __exit__(self, exc_type, exc_value, tb):
4452
if self.exit_hook is not None:
4553
self.exit_hook(self)
4654
return False
55+
56+
57+
def write_appmap(
58+
appmap, appmap_fname, recorder_type, metadata=None, basedir=Env.current.output_dir
59+
):
60+
"""Write an appmap file into basedir.
61+
62+
Adds APPMAP_SUFFIX to basename; shortens the name if necessary.
63+
Atomically replaces existing files. Creates the basedir if required.
64+
"""
65+
66+
if len(appmap_fname) > NAME_MAX - len(APPMAP_SUFFIX):
67+
part = NAME_MAX - len(APPMAP_SUFFIX) - 1 - HASH_LEN
68+
appmap_fname = appmap_fname[:part] + "-" + name_hash(appmap_fname[part:])[:HASH_LEN]
69+
filename = appmap_fname + APPMAP_SUFFIX
70+
71+
basedir = basedir / recorder_type
72+
basedir.mkdir(parents=True, exist_ok=True)
73+
74+
with NamedTemporaryFile(mode="w", dir=basedir, delete=False) as tmp:
75+
tmp.write(generation.dump(appmap, metadata))
76+
appmap_file = basedir / filename
77+
logger.info("writing %s", appmap_file)
78+
os.replace(tmp.name, appmap_file)
79+
80+
81+
def initialize():
82+
if Env.current.enables("process", "false"):
83+
r = Recording()
84+
r.start()
85+
86+
def save_at_exit():
87+
nonlocal r
88+
r.stop()
89+
now = datetime.now(timezone.utc)
90+
appmap_name = now.isoformat(timespec="seconds").replace("+00:00", "Z")
91+
recorder_type = "process"
92+
metadata = {
93+
"name": appmap_name,
94+
"recorder": {
95+
"name": "process",
96+
"type": recorder_type,
97+
},
98+
}
99+
write_appmap(r, appmap_name, recorder_type, metadata)
100+
101+
atexit.register(save_at_exit)

0 commit comments

Comments
 (0)