Repository: elastic/apm-agent-python

Issue:
Process pid get master pid 
**Process pid get master pid**

**Environment**
- OS:  Linux
- Python version: All versions
- Framework and version : All Versons
- APM Server version: All Versions
- Agent version: All Versions


https://github.com/elastic/apm-agent-python/blob/master/elasticapm/base.py#134
```python
        transport_kwargs = {
            "metadata": self._build_metadata(),
            "headers": headers,
            "verify_server_cert": self.config.verify_server_cert,
            "server_cert": self.config.server_cert,
            "timeout": self.config.server_timeout,
            "processors": self.load_processors(),
        }
```

Because we get pid in master process but not in workers after fork




Discussion:
```python
    def start_threads(self):
        with self._thread_starter_lock:
            current_pid = os.getpid()
            if self._pid != current_pid:
                self._transport._metadata['process']['pid'] = current_pid
                self._transport._metadata['process']['ppid'] = os.getppid()
                self.logger.debug("Detected PID change from %r to %r, starting threads", self._pid, current_pid)
                for manager_type, manager in self._thread_managers.items():
                    self.logger.debug("Starting %s thread", manager_type)
                    manager.start_thread()
                self._pid = current_pid
```

I fix thix issue by adding two lines of code in https://github.com/elastic/apm-agent-python/blob/master/elasticapm/base.py#L206:

```
self._transport._metadata['process']['pid'] = current_pid
self._transport._metadata['process']['ppid'] = os.getppid()
```
it works but not graceful

@zhouguangming This looks like a good fix to keep that metadata up to date. We'd love it if you opened a PR with this fix, but if you don't have time I can do it for you.
sorry for the late， dont mind to fix it if you want @basepi 

Resolving patch:
diff --git a/elasticapm/base.py b/elasticapm/base.py
--- a/elasticapm/base.py
+++ b/elasticapm/base.py
@@ -132,7 +132,6 @@ def __init__(self, config=None, **inline):
         }
 
         transport_kwargs = {
-            "metadata": self._build_metadata(),
             "headers": headers,
             "verify_server_cert": self.config.verify_server_cert,
             "server_cert": self.config.server_cert,
@@ -361,7 +360,7 @@ def get_system_info(self):
             system_data["kubernetes"] = k8s
         return system_data
 
-    def _build_metadata(self):
+    def build_metadata(self):
         data = {
             "service": self.get_service_info(),
             "process": self.get_process_info(),
diff --git a/elasticapm/transport/base.py b/elasticapm/transport/base.py
--- a/elasticapm/transport/base.py
+++ b/elasticapm/transport/base.py
@@ -64,7 +64,6 @@ class Transport(ThreadManager):
     def __init__(
         self,
         client,
-        metadata=None,
         compress_level=5,
         json_serializer=json_encoder.dumps,
         queue_chill_count=500,
@@ -75,14 +74,13 @@ def __init__(
         """
         Create a new Transport instance
 
-        :param metadata: Metadata object to prepend to every queue
         :param compress_level: GZip compress level. If zero, no GZip compression will be used
         :param json_serializer: serializer to use for JSON encoding
         :param kwargs:
         """
         self.client = client
         self.state = TransportState()
-        self._metadata = metadata if metadata is not None else {}
+        self._metadata = None
         self._compress_level = min(9, max(0, compress_level if compress_level is not None else 0))
         self._json_serializer = json_serializer
         self._queued_data = None
@@ -232,6 +230,9 @@ def _flush(self, buffer):
     def start_thread(self, pid=None):
         super(Transport, self).start_thread(pid=pid)
         if (not self._thread or self.pid != self._thread.pid) and not self._closed:
+            # Rebuild the metadata to capture new process information
+            if self.client:
+                self._metadata = self.client.build_metadata()
             try:
                 self._thread = threading.Thread(target=self._process_queue, name="eapm event processor thread")
                 self._thread.daemon = True
diff --git a/elasticapm/transport/http_base.py b/elasticapm/transport/http_base.py
--- a/elasticapm/transport/http_base.py
+++ b/elasticapm/transport/http_base.py
@@ -42,7 +42,6 @@ def __init__(
         client,
         verify_server_cert=True,
         compress_level=5,
-        metadata=None,
         headers=None,
         timeout=None,
         server_cert=None,
@@ -62,7 +61,7 @@ def __init__(
         }
         base, sep, tail = self._url.rpartition(constants.EVENTS_API_PATH)
         self._config_url = "".join((base, constants.AGENT_CONFIG_PATH, tail))
-        super(HTTPTransportBase, self).__init__(client, metadata=metadata, compress_level=compress_level, **kwargs)
+        super(HTTPTransportBase, self).__init__(client, compress_level=compress_level, **kwargs)
 
     def send(self, data):
         """


Test patch:
diff --git a/tests/client/client_tests.py b/tests/client/client_tests.py
--- a/tests/client/client_tests.py
+++ b/tests/client/client_tests.py
@@ -105,7 +105,7 @@ def test_system_info_hostname_configurable(elasticapm_client):
 
 @pytest.mark.parametrize("elasticapm_client", [{"global_labels": "az=us-east-1,az.rack=8"}], indirect=True)
 def test_global_labels(elasticapm_client):
-    data = elasticapm_client._build_metadata()
+    data = elasticapm_client.build_metadata()
     assert data["labels"] == {"az": "us-east-1", "az_rack": "8"}
 
 
diff --git a/tests/transports/test_base.py b/tests/transports/test_base.py
--- a/tests/transports/test_base.py
+++ b/tests/transports/test_base.py
@@ -86,7 +86,7 @@ def test_transport_state_set_success():
 @mock.patch("elasticapm.transport.base.Transport.send")
 @pytest.mark.parametrize("elasticapm_client", [{"api_request_time": "5s"}], indirect=True)
 def test_empty_queue_flush_is_not_sent(mock_send, elasticapm_client):
-    transport = Transport(client=elasticapm_client, metadata={"x": "y"})
+    transport = Transport(client=elasticapm_client)
     try:
         transport.start_thread()
         transport.flush()
@@ -98,7 +98,7 @@ def test_empty_queue_flush_is_not_sent(mock_send, elasticapm_client):
 @mock.patch("elasticapm.transport.base.Transport.send")
 @pytest.mark.parametrize("elasticapm_client", [{"api_request_time": "5s"}], indirect=True)
 def test_metadata_prepended(mock_send, elasticapm_client):
-    transport = Transport(client=elasticapm_client, metadata={"x": "y"}, compress_level=0)
+    transport = Transport(client=elasticapm_client, compress_level=0)
     transport.start_thread()
     transport.queue("error", {}, flush=True)
     transport.close()
@@ -116,7 +116,7 @@ def test_metadata_prepended(mock_send, elasticapm_client):
 @pytest.mark.parametrize("elasticapm_client", [{"api_request_time": "100ms"}], indirect=True)
 def test_flush_time(mock_send, caplog, elasticapm_client):
     with caplog.at_level("DEBUG", "elasticapm.transport"):
-        transport = Transport(client=elasticapm_client, metadata={})
+        transport = Transport(client=elasticapm_client)
         transport.start_thread()
         # let first run finish
         time.sleep(0.2)
@@ -130,7 +130,7 @@ def test_flush_time(mock_send, caplog, elasticapm_client):
 def test_api_request_time_dynamic(mock_send, caplog, elasticapm_client):
     elasticapm_client.config.update(version="1", api_request_time="1s")
     with caplog.at_level("DEBUG", "elasticapm.transport"):
-        transport = Transport(client=elasticapm_client, metadata={})
+        transport = Transport(client=elasticapm_client)
         transport.start_thread()
         # let first run finish
         time.sleep(0.2)
@@ -139,7 +139,7 @@ def test_api_request_time_dynamic(mock_send, caplog, elasticapm_client):
     assert mock_send.call_count == 0
     elasticapm_client.config.update(version="1", api_request_time="100ms")
     with caplog.at_level("DEBUG", "elasticapm.transport"):
-        transport = Transport(client=elasticapm_client, metadata={})
+        transport = Transport(client=elasticapm_client)
         transport.start_thread()
         # let first run finish
         time.sleep(0.2)
@@ -152,7 +152,7 @@ def test_api_request_time_dynamic(mock_send, caplog, elasticapm_client):
 @mock.patch("elasticapm.transport.base.Transport._flush")
 def test_api_request_size_dynamic(mock_flush, caplog, elasticapm_client):
     elasticapm_client.config.update(version="1", api_request_size="100b")
-    transport = Transport(client=elasticapm_client, metadata={}, queue_chill_count=1)
+    transport = Transport(client=elasticapm_client, queue_chill_count=1)
     transport.start_thread()
     try:
         with caplog.at_level("DEBUG", "elasticapm.transport"):
@@ -176,7 +176,7 @@ def test_api_request_size_dynamic(mock_flush, caplog, elasticapm_client):
 @mock.patch("elasticapm.transport.base.Transport._flush")
 @pytest.mark.parametrize("elasticapm_client", [{"api_request_size": "100b"}], indirect=True)
 def test_flush_time_size(mock_flush, caplog, elasticapm_client):
-    transport = Transport(client=elasticapm_client, metadata={}, queue_chill_count=1)
+    transport = Transport(client=elasticapm_client, queue_chill_count=1)
     transport.start_thread()
     try:
         with caplog.at_level("DEBUG", "elasticapm.transport"):
@@ -192,7 +192,7 @@ def test_flush_time_size(mock_flush, caplog, elasticapm_client):
 @mock.patch("elasticapm.transport.base.Transport.send")
 @pytest.mark.parametrize("elasticapm_client", [{"api_request_size": "1000b"}], indirect=True)
 def test_forced_flush(mock_send, caplog, elasticapm_client):
-    transport = Transport(client=elasticapm_client, metadata={}, compress_level=0)
+    transport = Transport(client=elasticapm_client, compress_level=0)
     transport.start_thread()
     try:
         with caplog.at_level("DEBUG", "elasticapm.transport"):
@@ -240,3 +240,12 @@ def test_compress_level_sanitization():
     assert DummyTransport(compress_level=None, url="", client=None)._compress_level == 0
     assert DummyTransport(compress_level=-1, url="", client=None)._compress_level == 0
     assert DummyTransport(compress_level=10, url="", client=None)._compress_level == 9
+
+
+@mock.patch("elasticapm.transport.base.Transport.send")
+def test_transport_metadata_pid_change(mock_send, elasticapm_client):
+    transport = Transport(client=elasticapm_client)
+    assert not transport._metadata
+    transport.start_thread()
+    assert transport._metadata
+    transport.close()