Prevent error in stats to stop pipeline processing

When there is an exception emitting stats for a node request, this will
cause the pipeline processing to be aborted.

In one particular case this was due to a non-ascii character in a label.

In addition to handling that exception we will also make sure to
normalize the request labels and replace all non-ascii chars.

ERROR zuul.Scheduler: Exception in pipeline processing:
Traceback (most recent call last):
  File ""/opt/zuul/lib/python3.13/site-packages/zuul/scheduler.py"", line 2453, in _process_pipeline
    while not self._stopped and manager.processQueue(
                                ~~~~~~~~~~~~~~~~~~~~^
            tenant_lock):
            ^^^^^^^^^^^^
  File ""/opt/zuul/lib/python3.13/site-packages/zuul/manager/__init__.py"", line 2078, in processQueue
    item_changed, nnfi = self._processOneItem(
                         ~~~~~~~~~~~~~~~~~~~~^
        item, nnfi)
        ^^^^^^^^^^^
  File ""/opt/zuul/lib/python3.13/site-packages/zuul/manager/__init__.py"", line 1959, in _processOneItem
    if ready and self.provisionNodes(item):
                 ~~~~~~~~~~~~~~~~~~~^^^^^^
  File ""/opt/zuul/lib/python3.13/site-packages/zuul/manager/__init__.py"", line 1192, in provisionNodes
    self._makeNodepoolRequest(
    ~~~~~~~~~~~~~~~~~~~~~~~~~^
        log, build_set, job, relative_priority)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ""/opt/zuul/lib/python3.13/site-packages/zuul/manager/__init__.py"", line 1216, in _makeNodepoolRequest
    req = self.sched.nodepool.requestNodes(
        build_set.uuid, job, tenant_name, pipeline_name, provider,
        priority, relative_priority, event=item.event)
  File ""/opt/zuul/lib/python3.13/site-packages/zuul/nodepool.py"", line 201, in requestNodes
    self.emitStats(req)
    ~~~~~~~~~~~~~~^^^^^
  File ""/opt/zuul/lib/python3.13/site-packages/zuul/nodepool.py"", line 169, in emitStats
    pipe.send()
    ~~~~~~~~~^^
  File ""/opt/zuul/lib/python3.13/site-packages/statsd/client/base.py"", line 102, in send
    self._send()
    ~~~~~~~~~~^^
  File ""/opt/zuul/lib/python3.13/site-packages/statsd/client/udp.py"", line 22, in _send
    self._client._after(data)
    ~~~~~~~~~~~~~~~~~~~^^^^^^
  File ""/opt/zuul/lib/python3.13/site-packages/statsd/client/base.py"", line 76, in _after
    self._send(data)
    ~~~~~~~~~~^^^^^^
  File ""/opt/zuul/lib/python3.13/site-packages/statsd/client/udp.py"", line 42, in _send
    self._sock.sendto(data.encode('ascii'), self._addr)
                      ~~~~~~~~~~~^^^^^^^^^
UnicodeEncodeError: 'ascii' codec can't encode character '\xe7' in position 156: ordinal not in range(128)"

Change-Id: Id1db2aa706e5ec235443e7abdba296c83bcade81
This commit is contained in:
Simon Westphahl
2025-10-23 15:50:32 +02:00
parent 4e2edf509c
commit 12d3393816
2 changed files with 12 additions and 2 deletions
+2
View File
@@ -26,6 +26,8 @@ def get_statsd_config(config):
def normalize_statsd_name(name):
name = name.replace('.', '_')
name = name.replace(':', '_')
# Statsd doesn't support non-ascii chars
name = name.encode('ascii', 'replace').decode()
return name
+10 -2
View File
@@ -18,6 +18,7 @@ from collections import defaultdict
from zuul import model
from zuul.lib import tracing
from zuul.lib.logutil import get_annotated_logger
from zuul.lib.statsd import normalize_statsd_name
from zuul.zk.event_queues import (
PipelineResultEventQueue,
NodepoolEventElection
@@ -136,6 +137,12 @@ class Nodepool(object):
raise
def emitStats(self, request):
try:
return self._emitStats(request)
except Exception:
self.log.exception("Failed to emit stats:")
def _emitStats(self, request):
# Implements the following :
# counter zuul.nodepool.requests.<state>.total
# counter zuul.nodepool.requests.<state>.label.<label>
@@ -160,9 +167,10 @@ class Nodepool(object):
if dt:
pipe.timing(key, dt)
for label in request.labels:
pipe.incr(key + '.label.%s' % label)
safe_label = normalize_statsd_name(label)
pipe.incr(key + '.label.%s' % safe_label)
if dt:
pipe.timing(key + '.label.%s' % label, dt)
pipe.timing(key + '.label.%s' % safe_label, dt)
pipe.incr(key + '.size.%s' % len(request.labels))
if dt:
pipe.timing(key + '.size.%s' % len(request.labels), dt)