|
13 | 13 | import os
|
14 | 14 | import socket
|
15 | 15 | import errno
|
| 16 | +import struct |
16 | 17 | import threading
|
17 | 18 | import time
|
18 | 19 | from threading import Lock, RLock
|
|
49 | 50 | DEFAULT_HOST = "localhost"
|
50 | 51 | DEFAULT_PORT = 8125
|
51 | 52 |
|
| 53 | +# Socket prefixes |
| 54 | +UNIX_ADDRESS_SCHEME = "unix://" |
| 55 | +UNIX_ADDRESS_DATAGRAM_SCHEME = "unixgram://" |
| 56 | +UNIX_ADDRESS_STREAM_SCHEME = "unixstream://" |
| 57 | + |
52 | 58 | # Buffering-related values (in seconds)
|
53 |
| -DEFAULT_FLUSH_INTERVAL = 0.3 |
| 59 | +DEFAULT_BUFFERING_FLUSH_INTERVAL = 0.3 |
54 | 60 | MIN_FLUSH_INTERVAL = 0.0001
|
55 | 61 |
|
56 | 62 | # Env var to enable/disable sending the container ID field
|
@@ -145,7 +151,7 @@ def __init__(
|
145 | 151 | host=DEFAULT_HOST, # type: Text
|
146 | 152 | port=DEFAULT_PORT, # type: int
|
147 | 153 | max_buffer_size=None, # type: None
|
148 |
| - flush_interval=DEFAULT_FLUSH_INTERVAL, # type: float |
| 154 | + flush_interval=DEFAULT_BUFFERING_FLUSH_INTERVAL, # type: float |
149 | 155 | disable_aggregation=True, # type: bool
|
150 | 156 | disable_buffering=True, # type: bool
|
151 | 157 | namespace=None, # type: Optional[Text]
|
@@ -489,6 +495,30 @@ def socket_path(self, path):
|
489 | 495 | self._transport = "uds"
|
490 | 496 | self._max_payload_size = self._max_buffer_len or UDS_OPTIMAL_PAYLOAD_LENGTH
|
491 | 497 |
|
| 498 | + @property |
| 499 | + def socket(self): |
| 500 | + return self._socket |
| 501 | + |
| 502 | + @socket.setter |
| 503 | + def socket(self, new_socket): |
| 504 | + self._socket = new_socket |
| 505 | + if new_socket: |
| 506 | + self._socket_kind = new_socket.getsockopt(socket.SOL_SOCKET, socket.SO_TYPE) |
| 507 | + else: |
| 508 | + self._socket_kind = None |
| 509 | + |
| 510 | + @property |
| 511 | + def telemetry_socket(self): |
| 512 | + return self._telemetry_socket |
| 513 | + |
| 514 | + @telemetry_socket.setter |
| 515 | + def telemetry_socket(self, t_socket): |
| 516 | + self._telemetry_socket = t_socket |
| 517 | + if t_socket: |
| 518 | + self._telemetry_socket_kind = t_socket.getsockopt(socket.SOL_SOCKET, socket.SO_TYPE) |
| 519 | + else: |
| 520 | + self._telemetry_socket_kind = None |
| 521 | + |
492 | 522 | def enable_background_sender(self, sender_queue_size=0, sender_queue_timeout=0):
|
493 | 523 | """
|
494 | 524 | Use a background thread to communicate with the dogstatsd server.
|
@@ -643,7 +673,7 @@ def disable_aggregation(self):
|
643 | 673 | self._stop_flush_thread()
|
644 | 674 | log.debug("Statsd aggregation is disabled")
|
645 | 675 |
|
646 |
| - def enable_aggregation(self, flush_interval=DEFAULT_FLUSH_INTERVAL): |
| 676 | + def enable_aggregation(self, flush_interval=DEFAULT_BUFFERING_FLUSH_INTERVAL): |
647 | 677 | with self._config_lock:
|
648 | 678 | if not self._disable_aggregation:
|
649 | 679 | return
|
@@ -731,11 +761,37 @@ def _ensure_min_send_buffer_size(cls, sock, min_size=MIN_SEND_BUFFER_SIZE):
|
731 | 761 |
|
732 | 762 | @classmethod
|
733 | 763 | def _get_uds_socket(cls, socket_path, timeout):
|
734 |
| - sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) |
735 |
| - sock.settimeout(timeout) |
736 |
| - cls._ensure_min_send_buffer_size(sock) |
737 |
| - sock.connect(socket_path) |
738 |
| - return sock |
| 764 | + valid_socket_kinds = [socket.SOCK_DGRAM, socket.SOCK_STREAM] |
| 765 | + if socket_path.startswith(UNIX_ADDRESS_DATAGRAM_SCHEME): |
| 766 | + valid_socket_kinds = [socket.SOCK_DGRAM] |
| 767 | + socket_path = socket_path[len(UNIX_ADDRESS_DATAGRAM_SCHEME):] |
| 768 | + elif socket_path.startswith(UNIX_ADDRESS_STREAM_SCHEME): |
| 769 | + valid_socket_kinds = [socket.SOCK_STREAM] |
| 770 | + socket_path = socket_path[len(UNIX_ADDRESS_STREAM_SCHEME):] |
| 771 | + elif socket_path.startswith(UNIX_ADDRESS_SCHEME): |
| 772 | + socket_path = socket_path[len(UNIX_ADDRESS_SCHEME):] |
| 773 | + |
| 774 | + last_error = ValueError("Invalid socket path") |
| 775 | + for socket_kind in valid_socket_kinds: |
| 776 | + # py2 stores socket kinds differently than py3, determine the name independently from version |
| 777 | + sk_name = { socket.SOCK_STREAM: "stream", socket.SOCK_DGRAM: "datagram" }[socket_kind] |
| 778 | + |
| 779 | + try: |
| 780 | + sock = socket.socket(socket.AF_UNIX, socket_kind) |
| 781 | + sock.settimeout(timeout) |
| 782 | + cls._ensure_min_send_buffer_size(sock) |
| 783 | + sock.connect(socket_path) |
| 784 | + log.debug("Connected to socket %s with kind %s", socket_path, sk_name) |
| 785 | + return sock |
| 786 | + except Exception as e: |
| 787 | + if sock is not None: |
| 788 | + sock.close() |
| 789 | + log.debug("Failed to connect to %s with kind %s: %s", socket_path, sk_name, e) |
| 790 | + if e.errno == errno.EPROTOTYPE: |
| 791 | + last_error = e |
| 792 | + continue |
| 793 | + raise e |
| 794 | + raise last_error |
739 | 795 |
|
740 | 796 | @classmethod
|
741 | 797 | def _get_udp_socket(cls, host, port, timeout):
|
@@ -805,6 +861,9 @@ def _reset_buffer(self):
|
805 | 861 | self._current_buffer_total_size = 0
|
806 | 862 | self._buffer = []
|
807 | 863 |
|
| 864 | + def flush(self): |
| 865 | + self.flush_buffered_metrics() |
| 866 | + |
808 | 867 | def flush_buffered_metrics(self):
|
809 | 868 | """
|
810 | 869 | Flush the metrics buffer by sending the data to the server.
|
@@ -1216,10 +1275,14 @@ def _xmit_packet(self, packet, is_telemetry):
|
1216 | 1275 | try:
|
1217 | 1276 | if is_telemetry and self._dedicated_telemetry_destination():
|
1218 | 1277 | mysocket = self.telemetry_socket or self.get_socket(telemetry=True)
|
| 1278 | + socket_kind = self._telemetry_socket_kind |
1219 | 1279 | else:
|
1220 | 1280 | # If set, use socket directly
|
1221 | 1281 | mysocket = self.socket or self.get_socket()
|
| 1282 | + socket_kind = self._socket_kind |
1222 | 1283 |
|
| 1284 | + if socket_kind == socket.SOCK_STREAM: |
| 1285 | + mysocket.send(struct.pack('<I', len(packet))) |
1223 | 1286 | mysocket.send(packet.encode(self.encoding))
|
1224 | 1287 |
|
1225 | 1288 | if not is_telemetry and self._telemetry:
|
@@ -1253,7 +1316,7 @@ def _xmit_packet(self, packet, is_telemetry):
|
1253 | 1316 | )
|
1254 | 1317 | self.close_socket()
|
1255 | 1318 | except Exception as exc:
|
1256 |
| - print("Unexpected error: %s", exc) |
| 1319 | + print("Unexpected error: ", exc) |
1257 | 1320 | log.error("Unexpected error: %s", str(exc))
|
1258 | 1321 |
|
1259 | 1322 | if not is_telemetry and self._telemetry:
|
|
0 commit comments