diff options
author | robot-piglet <robot-piglet@yandex-team.com> | 2024-04-01 04:03:58 +0300 |
---|---|---|
committer | robot-piglet <robot-piglet@yandex-team.com> | 2024-04-01 04:15:53 +0300 |
commit | 6e7835911e36724b4c14f4888d6f0222994eadf0 (patch) | |
tree | 7f6210d918014bc308e98934e896d34aadb9bac0 | |
parent | 8e91894b5642604c33982805651a0f59bcdaf137 (diff) | |
download | ydb-6e7835911e36724b4c14f4888d6f0222994eadf0.tar.gz |
Intermediate changes
-rw-r--r-- | contrib/python/fonttools/.dist-info/METADATA | 9 | ||||
-rw-r--r-- | contrib/python/fonttools/fontTools/__init__.py | 2 | ||||
-rw-r--r-- | contrib/python/fonttools/fontTools/cu2qu/ufo.py | 12 | ||||
-rw-r--r-- | contrib/python/fonttools/fontTools/pens/basePen.py | 50 | ||||
-rw-r--r-- | contrib/python/fonttools/fontTools/pens/filterPen.py | 86 | ||||
-rw-r--r-- | contrib/python/fonttools/fontTools/pens/pointPen.py | 79 | ||||
-rw-r--r-- | contrib/python/fonttools/fontTools/pens/recordingPen.py | 158 | ||||
-rw-r--r-- | contrib/python/fonttools/fontTools/varLib/instancer/__init__.py | 6 | ||||
-rw-r--r-- | contrib/python/fonttools/ya.make | 2 | ||||
-rw-r--r-- | yt/yt/library/profiling/solomon/exporter.cpp | 5 | ||||
-rw-r--r-- | yt/yt/library/profiling/solomon/exporter.h | 2 | ||||
-rw-r--r-- | yt/yt/library/profiling/solomon/private.h | 2 | ||||
-rw-r--r-- | yt/yt/library/profiling/solomon/producer.cpp | 127 | ||||
-rw-r--r-- | yt/yt/library/profiling/solomon/producer.h | 4 | ||||
-rw-r--r-- | yt/yt/library/profiling/solomon/registry.cpp | 5 | ||||
-rw-r--r-- | yt/yt/library/profiling/solomon/registry.h | 1 |
16 files changed, 468 insertions, 82 deletions
diff --git a/contrib/python/fonttools/.dist-info/METADATA b/contrib/python/fonttools/.dist-info/METADATA index a711f82b4e..b374ebc499 100644 --- a/contrib/python/fonttools/.dist-info/METADATA +++ b/contrib/python/fonttools/.dist-info/METADATA @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: fonttools -Version: 4.49.0 +Version: 4.50.0 Summary: Tools to manipulate font files Home-page: http://github.com/fonttools/fonttools Author: Just van Rossum @@ -375,6 +375,13 @@ Have fun! Changelog ~~~~~~~~~ +4.50.0 (released 2024-03-15) +---------------------------- + +- [pens] Added decomposing filter pens that draw components as regular contours (#3460). +- [instancer] Drop explicit no-op axes from TupleVariations (#3457). +- [cu2qu/ufo] Return set of modified glyph names from fonts_to_quadratic (#3456). + 4.49.0 (released 2024-02-15) ---------------------------- diff --git a/contrib/python/fonttools/fontTools/__init__.py b/contrib/python/fonttools/fontTools/__init__.py index e6a745bd52..ead49e26c8 100644 --- a/contrib/python/fonttools/fontTools/__init__.py +++ b/contrib/python/fonttools/fontTools/__init__.py @@ -3,6 +3,6 @@ from fontTools.misc.loggingTools import configLogger log = logging.getLogger(__name__) -version = __version__ = "4.49.0" +version = __version__ = "4.50.0" __all__ = ["version", "log", "configLogger"] diff --git a/contrib/python/fonttools/fontTools/cu2qu/ufo.py b/contrib/python/fonttools/fontTools/cu2qu/ufo.py index 10367cfecf..7a6dbc67f8 100644 --- a/contrib/python/fonttools/fontTools/cu2qu/ufo.py +++ b/contrib/python/fonttools/fontTools/cu2qu/ufo.py @@ -250,7 +250,7 @@ def fonts_to_quadratic( compatibility. If this is not required, calling fonts_to_quadratic with one font at a time may yield slightly more optimized results. - Return True if fonts were modified, else return False. + Return the set of modified glyph names if any, else return an empty set. By default, cu2qu stores the curve type in the fonts' lib, under a private key "com.github.googlei18n.cu2qu.curve_type", and will not try to convert @@ -296,7 +296,7 @@ def fonts_to_quadratic( elif max_err_em: max_errors = [f.info.unitsPerEm * max_err_em for f in fonts] - modified = False + modified = set() glyph_errors = {} for name in set().union(*(f.keys() for f in fonts)): glyphs = [] @@ -306,9 +306,10 @@ def fonts_to_quadratic( glyphs.append(font[name]) cur_max_errors.append(error) try: - modified |= _glyphs_to_quadratic( + if _glyphs_to_quadratic( glyphs, cur_max_errors, reverse_direction, stats, all_quadratic - ) + ): + modified.add(name) except IncompatibleGlyphsError as exc: logger.error(exc) glyph_errors[name] = exc @@ -329,7 +330,6 @@ def fonts_to_quadratic( new_curve_type = "quadratic" if all_quadratic else "mixed" if curve_type != new_curve_type: font.lib[CURVE_TYPE_LIB_KEY] = new_curve_type - modified = True return modified @@ -343,7 +343,7 @@ def glyph_to_quadratic(glyph, **kwargs): def font_to_quadratic(font, **kwargs): """Convenience wrapper around fonts_to_quadratic, for just one font. - Return True if the font was modified, else return False. + Return the set of modified glyph names if any, else return empty set. """ return fonts_to_quadratic([font], **kwargs) diff --git a/contrib/python/fonttools/fontTools/pens/basePen.py b/contrib/python/fonttools/fontTools/pens/basePen.py index 5d2cf5032c..ba38f70090 100644 --- a/contrib/python/fonttools/fontTools/pens/basePen.py +++ b/contrib/python/fonttools/fontTools/pens/basePen.py @@ -39,7 +39,7 @@ sequence of length 2 will do. from typing import Tuple, Dict from fontTools.misc.loggingTools import LogMixin -from fontTools.misc.transform import DecomposedTransform +from fontTools.misc.transform import DecomposedTransform, Identity __all__ = [ "AbstractPen", @@ -195,17 +195,40 @@ class DecomposingPen(LoggingPen): By default a warning message is logged when a base glyph is missing; set the class variable ``skipMissingComponents`` to False if you want - to raise a :class:`MissingComponentError` exception. + all instances of a sub-class to raise a :class:`MissingComponentError` + exception by default. """ skipMissingComponents = True + # alias error for convenience + MissingComponentError = MissingComponentError - def __init__(self, glyphSet): - """Takes a single 'glyphSet' argument (dict), in which the glyphs - that are referenced as components are looked up by their name. + def __init__( + self, + glyphSet, + *args, + skipMissingComponents=None, + reverseFlipped=False, + **kwargs, + ): + """Takes a 'glyphSet' argument (dict), in which the glyphs that are referenced + as components are looked up by their name. + + If the optional 'reverseFlipped' argument is True, components whose transformation + matrix has a negative determinant will be decomposed with a reversed path direction + to compensate for the flip. + + The optional 'skipMissingComponents' argument can be set to True/False to + override the homonymous class attribute for a given pen instance. """ - super(DecomposingPen, self).__init__() + super(DecomposingPen, self).__init__(*args, **kwargs) self.glyphSet = glyphSet + self.skipMissingComponents = ( + self.__class__.skipMissingComponents + if skipMissingComponents is None + else skipMissingComponents + ) + self.reverseFlipped = reverseFlipped def addComponent(self, glyphName, transformation): """Transform the points of the base glyph and draw it onto self.""" @@ -218,8 +241,19 @@ class DecomposingPen(LoggingPen): raise MissingComponentError(glyphName) self.log.warning("glyph '%s' is missing from glyphSet; skipped" % glyphName) else: - tPen = TransformPen(self, transformation) - glyph.draw(tPen) + pen = self + if transformation != Identity: + pen = TransformPen(pen, transformation) + if self.reverseFlipped: + # if the transformation has a negative determinant, it will + # reverse the contour direction of the component + a, b, c, d = transformation[:4] + det = a * d - b * c + if det < 0: + from fontTools.pens.reverseContourPen import ReverseContourPen + + pen = ReverseContourPen(pen) + glyph.draw(pen) def addVarComponent(self, glyphName, transformation, location): # GlyphSet decomposes for us diff --git a/contrib/python/fonttools/fontTools/pens/filterPen.py b/contrib/python/fonttools/fontTools/pens/filterPen.py index 6c8712c261..f104e67dd3 100644 --- a/contrib/python/fonttools/fontTools/pens/filterPen.py +++ b/contrib/python/fonttools/fontTools/pens/filterPen.py @@ -1,5 +1,7 @@ -from fontTools.pens.basePen import AbstractPen -from fontTools.pens.pointPen import AbstractPointPen +from __future__ import annotations + +from fontTools.pens.basePen import AbstractPen, DecomposingPen +from fontTools.pens.pointPen import AbstractPointPen, DecomposingPointPen from fontTools.pens.recordingPen import RecordingPen @@ -150,8 +152,8 @@ class FilterPointPen(_PassThruComponentsMixin, AbstractPointPen): ('endPath', (), {}) """ - def __init__(self, outPointPen): - self._outPen = outPointPen + def __init__(self, outPen): + self._outPen = outPen def beginPath(self, **kwargs): self._outPen.beginPath(**kwargs) @@ -161,3 +163,79 @@ class FilterPointPen(_PassThruComponentsMixin, AbstractPointPen): def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs): self._outPen.addPoint(pt, segmentType, smooth, name, **kwargs) + + +class _DecomposingFilterPenMixin: + """Mixin class that decomposes components as regular contours. + + Shared by both DecomposingFilterPen and DecomposingFilterPointPen. + + Takes two required parameters, another (segment or point) pen 'outPen' to draw + with, and a 'glyphSet' dict of drawable glyph objects to draw components from. + + The 'skipMissingComponents' and 'reverseFlipped' optional arguments work the + same as in the DecomposingPen/DecomposingPointPen. Both are False by default. + + In addition, the decomposing filter pens also take the following two options: + + 'include' is an optional set of component base glyph names to consider for + decomposition; the default include=None means decompose all components no matter + the base glyph name). + + 'decomposeNested' (bool) controls whether to recurse decomposition into nested + components of components (this only matters when 'include' was also provided); + if False, only decompose top-level components included in the set, but not + also their children. + """ + + # raises MissingComponentError if base glyph is not found in glyphSet + skipMissingComponents = False + + def __init__( + self, + outPen, + glyphSet, + skipMissingComponents=None, + reverseFlipped=False, + include: set[str] | None = None, + decomposeNested: bool = True, + ): + super().__init__( + outPen=outPen, + glyphSet=glyphSet, + skipMissingComponents=skipMissingComponents, + reverseFlipped=reverseFlipped, + ) + self.include = include + self.decomposeNested = decomposeNested + + def addComponent(self, baseGlyphName, transformation, **kwargs): + # only decompose the component if it's included in the set + if self.include is None or baseGlyphName in self.include: + # if we're decomposing nested components, temporarily set include to None + include_bak = self.include + if self.decomposeNested and self.include: + self.include = None + try: + super().addComponent(baseGlyphName, transformation, **kwargs) + finally: + if self.include != include_bak: + self.include = include_bak + else: + _PassThruComponentsMixin.addComponent( + self, baseGlyphName, transformation, **kwargs + ) + + +class DecomposingFilterPen(_DecomposingFilterPenMixin, DecomposingPen, FilterPen): + """Filter pen that draws components as regular contours.""" + + pass + + +class DecomposingFilterPointPen( + _DecomposingFilterPenMixin, DecomposingPointPen, FilterPointPen +): + """Filter point pen that draws components as regular contours.""" + + pass diff --git a/contrib/python/fonttools/fontTools/pens/pointPen.py b/contrib/python/fonttools/fontTools/pens/pointPen.py index eb1ebc2048..93a9201c99 100644 --- a/contrib/python/fonttools/fontTools/pens/pointPen.py +++ b/contrib/python/fonttools/fontTools/pens/pointPen.py @@ -15,8 +15,9 @@ For instance, whether or not a point is smooth, and its name. import math from typing import Any, Optional, Tuple, Dict -from fontTools.pens.basePen import AbstractPen, PenError -from fontTools.misc.transform import DecomposedTransform +from fontTools.misc.loggingTools import LogMixin +from fontTools.pens.basePen import AbstractPen, MissingComponentError, PenError +from fontTools.misc.transform import DecomposedTransform, Identity __all__ = [ "AbstractPointPen", @@ -523,3 +524,77 @@ class ReverseContourPointPen(AbstractPointPen): if self.currentContour is not None: raise PenError("Components must be added before or after contours") self.pen.addComponent(glyphName, transform, identifier=identifier, **kwargs) + + +class DecomposingPointPen(LogMixin, AbstractPointPen): + """Implements a 'addComponent' method that decomposes components + (i.e. draws them onto self as simple contours). + It can also be used as a mixin class (e.g. see DecomposingRecordingPointPen). + + You must override beginPath, addPoint, endPath. You may + additionally override addVarComponent and addComponent. + + By default a warning message is logged when a base glyph is missing; + set the class variable ``skipMissingComponents`` to False if you want + all instances of a sub-class to raise a :class:`MissingComponentError` + exception by default. + """ + + skipMissingComponents = True + # alias error for convenience + MissingComponentError = MissingComponentError + + def __init__( + self, + glyphSet, + *args, + skipMissingComponents=None, + reverseFlipped=False, + **kwargs, + ): + """Takes a 'glyphSet' argument (dict), in which the glyphs that are referenced + as components are looked up by their name. + + If the optional 'reverseFlipped' argument is True, components whose transformation + matrix has a negative determinant will be decomposed with a reversed path direction + to compensate for the flip. + + The optional 'skipMissingComponents' argument can be set to True/False to + override the homonymous class attribute for a given pen instance. + """ + super().__init__(*args, **kwargs) + self.glyphSet = glyphSet + self.skipMissingComponents = ( + self.__class__.skipMissingComponents + if skipMissingComponents is None + else skipMissingComponents + ) + self.reverseFlipped = reverseFlipped + + def addComponent(self, baseGlyphName, transformation, identifier=None, **kwargs): + """Transform the points of the base glyph and draw it onto self. + + The `identifier` parameter and any extra kwargs are ignored. + """ + from fontTools.pens.transformPen import TransformPointPen + + try: + glyph = self.glyphSet[baseGlyphName] + except KeyError: + if not self.skipMissingComponents: + raise MissingComponentError(baseGlyphName) + self.log.warning( + "glyph '%s' is missing from glyphSet; skipped" % baseGlyphName + ) + else: + pen = self + if transformation != Identity: + pen = TransformPointPen(pen, transformation) + if self.reverseFlipped: + # if the transformation has a negative determinant, it will + # reverse the contour direction of the component + a, b, c, d = transformation[:4] + det = a * d - b * c + if a * d - b * c < 0: + pen = ReverseContourPointPen(pen) + glyph.drawPoints(pen) diff --git a/contrib/python/fonttools/fontTools/pens/recordingPen.py b/contrib/python/fonttools/fontTools/pens/recordingPen.py index 4f44a4d59f..ba165e1951 100644 --- a/contrib/python/fonttools/fontTools/pens/recordingPen.py +++ b/contrib/python/fonttools/fontTools/pens/recordingPen.py @@ -1,13 +1,14 @@ """Pen recording operations that can be accessed or replayed.""" from fontTools.pens.basePen import AbstractPen, DecomposingPen -from fontTools.pens.pointPen import AbstractPointPen +from fontTools.pens.pointPen import AbstractPointPen, DecomposingPointPen __all__ = [ "replayRecording", "RecordingPen", "DecomposingRecordingPen", + "DecomposingRecordingPointPen", "RecordingPointPen", "lerpRecordings", ] @@ -85,28 +86,55 @@ class DecomposingRecordingPen(DecomposingPen, RecordingPen): """Same as RecordingPen, except that it doesn't keep components as references, but draws them decomposed as regular contours. - The constructor takes a single 'glyphSet' positional argument, + The constructor takes a required 'glyphSet' positional argument, a dictionary of glyph objects (i.e. with a 'draw' method) keyed - by thir name:: - - >>> class SimpleGlyph(object): - ... def draw(self, pen): - ... pen.moveTo((0, 0)) - ... pen.curveTo((1, 1), (2, 2), (3, 3)) - ... pen.closePath() - >>> class CompositeGlyph(object): - ... def draw(self, pen): - ... pen.addComponent('a', (1, 0, 0, 1, -1, 1)) - >>> glyphSet = {'a': SimpleGlyph(), 'b': CompositeGlyph()} - >>> for name, glyph in sorted(glyphSet.items()): - ... pen = DecomposingRecordingPen(glyphSet) - ... glyph.draw(pen) - ... print("{}: {}".format(name, pen.value)) - a: [('moveTo', ((0, 0),)), ('curveTo', ((1, 1), (2, 2), (3, 3))), ('closePath', ())] - b: [('moveTo', ((-1, 1),)), ('curveTo', ((0, 2), (1, 3), (2, 4))), ('closePath', ())] + by thir name; other arguments are forwarded to the DecomposingPen's + constructor:: + + >>> class SimpleGlyph(object): + ... def draw(self, pen): + ... pen.moveTo((0, 0)) + ... pen.curveTo((1, 1), (2, 2), (3, 3)) + ... pen.closePath() + >>> class CompositeGlyph(object): + ... def draw(self, pen): + ... pen.addComponent('a', (1, 0, 0, 1, -1, 1)) + >>> class MissingComponent(object): + ... def draw(self, pen): + ... pen.addComponent('foobar', (1, 0, 0, 1, 0, 0)) + >>> class FlippedComponent(object): + ... def draw(self, pen): + ... pen.addComponent('a', (-1, 0, 0, 1, 0, 0)) + >>> glyphSet = { + ... 'a': SimpleGlyph(), + ... 'b': CompositeGlyph(), + ... 'c': MissingComponent(), + ... 'd': FlippedComponent(), + ... } + >>> for name, glyph in sorted(glyphSet.items()): + ... pen = DecomposingRecordingPen(glyphSet) + ... try: + ... glyph.draw(pen) + ... except pen.MissingComponentError: + ... pass + ... print("{}: {}".format(name, pen.value)) + a: [('moveTo', ((0, 0),)), ('curveTo', ((1, 1), (2, 2), (3, 3))), ('closePath', ())] + b: [('moveTo', ((-1, 1),)), ('curveTo', ((0, 2), (1, 3), (2, 4))), ('closePath', ())] + c: [] + d: [('moveTo', ((0, 0),)), ('curveTo', ((-1, 1), (-2, 2), (-3, 3))), ('closePath', ())] + >>> for name, glyph in sorted(glyphSet.items()): + ... pen = DecomposingRecordingPen( + ... glyphSet, skipMissingComponents=True, reverseFlipped=True, + ... ) + ... glyph.draw(pen) + ... print("{}: {}".format(name, pen.value)) + a: [('moveTo', ((0, 0),)), ('curveTo', ((1, 1), (2, 2), (3, 3))), ('closePath', ())] + b: [('moveTo', ((-1, 1),)), ('curveTo', ((0, 2), (1, 3), (2, 4))), ('closePath', ())] + c: [] + d: [('moveTo', ((0, 0),)), ('lineTo', ((-3, 3),)), ('curveTo', ((-2, 2), (-1, 1), (0, 0))), ('closePath', ())] """ - # raises KeyError if base glyph is not found in glyphSet + # raises MissingComponentError(KeyError) if base glyph is not found in glyphSet skipMissingComponents = False @@ -174,6 +202,96 @@ class RecordingPointPen(AbstractPointPen): drawPoints = replay +class DecomposingRecordingPointPen(DecomposingPointPen, RecordingPointPen): + """Same as RecordingPointPen, except that it doesn't keep components + as references, but draws them decomposed as regular contours. + + The constructor takes a required 'glyphSet' positional argument, + a dictionary of pointPen-drawable glyph objects (i.e. with a 'drawPoints' method) + keyed by thir name; other arguments are forwarded to the DecomposingPointPen's + constructor:: + + >>> from pprint import pprint + >>> class SimpleGlyph(object): + ... def drawPoints(self, pen): + ... pen.beginPath() + ... pen.addPoint((0, 0), "line") + ... pen.addPoint((1, 1)) + ... pen.addPoint((2, 2)) + ... pen.addPoint((3, 3), "curve") + ... pen.endPath() + >>> class CompositeGlyph(object): + ... def drawPoints(self, pen): + ... pen.addComponent('a', (1, 0, 0, 1, -1, 1)) + >>> class MissingComponent(object): + ... def drawPoints(self, pen): + ... pen.addComponent('foobar', (1, 0, 0, 1, 0, 0)) + >>> class FlippedComponent(object): + ... def drawPoints(self, pen): + ... pen.addComponent('a', (-1, 0, 0, 1, 0, 0)) + >>> glyphSet = { + ... 'a': SimpleGlyph(), + ... 'b': CompositeGlyph(), + ... 'c': MissingComponent(), + ... 'd': FlippedComponent(), + ... } + >>> for name, glyph in sorted(glyphSet.items()): + ... pen = DecomposingRecordingPointPen(glyphSet) + ... try: + ... glyph.drawPoints(pen) + ... except pen.MissingComponentError: + ... pass + ... pprint({name: pen.value}) + {'a': [('beginPath', (), {}), + ('addPoint', ((0, 0), 'line', False, None), {}), + ('addPoint', ((1, 1), None, False, None), {}), + ('addPoint', ((2, 2), None, False, None), {}), + ('addPoint', ((3, 3), 'curve', False, None), {}), + ('endPath', (), {})]} + {'b': [('beginPath', (), {}), + ('addPoint', ((-1, 1), 'line', False, None), {}), + ('addPoint', ((0, 2), None, False, None), {}), + ('addPoint', ((1, 3), None, False, None), {}), + ('addPoint', ((2, 4), 'curve', False, None), {}), + ('endPath', (), {})]} + {'c': []} + {'d': [('beginPath', (), {}), + ('addPoint', ((0, 0), 'line', False, None), {}), + ('addPoint', ((-1, 1), None, False, None), {}), + ('addPoint', ((-2, 2), None, False, None), {}), + ('addPoint', ((-3, 3), 'curve', False, None), {}), + ('endPath', (), {})]} + >>> for name, glyph in sorted(glyphSet.items()): + ... pen = DecomposingRecordingPointPen( + ... glyphSet, skipMissingComponents=True, reverseFlipped=True, + ... ) + ... glyph.drawPoints(pen) + ... pprint({name: pen.value}) + {'a': [('beginPath', (), {}), + ('addPoint', ((0, 0), 'line', False, None), {}), + ('addPoint', ((1, 1), None, False, None), {}), + ('addPoint', ((2, 2), None, False, None), {}), + ('addPoint', ((3, 3), 'curve', False, None), {}), + ('endPath', (), {})]} + {'b': [('beginPath', (), {}), + ('addPoint', ((-1, 1), 'line', False, None), {}), + ('addPoint', ((0, 2), None, False, None), {}), + ('addPoint', ((1, 3), None, False, None), {}), + ('addPoint', ((2, 4), 'curve', False, None), {}), + ('endPath', (), {})]} + {'c': []} + {'d': [('beginPath', (), {}), + ('addPoint', ((0, 0), 'curve', False, None), {}), + ('addPoint', ((-3, 3), 'line', False, None), {}), + ('addPoint', ((-2, 2), None, False, None), {}), + ('addPoint', ((-1, 1), None, False, None), {}), + ('endPath', (), {})]} + """ + + # raises MissingComponentError(KeyError) if base glyph is not found in glyphSet + skipMissingComponents = False + + def lerpRecordings(recording1, recording2, factor=0.5): """Linearly interpolate between two recordings. The recordings must be decomposed, i.e. they must not contain any components. diff --git a/contrib/python/fonttools/fontTools/varLib/instancer/__init__.py b/contrib/python/fonttools/fontTools/varLib/instancer/__init__.py index 89427dc534..c5de81cad0 100644 --- a/contrib/python/fonttools/fontTools/varLib/instancer/__init__.py +++ b/contrib/python/fonttools/fontTools/varLib/instancer/__init__.py @@ -531,9 +531,13 @@ def changeTupleVariationsAxisLimits(variations, axisLimits): def changeTupleVariationAxisLimit(var, axisTag, axisLimit): assert isinstance(axisLimit, NormalizedAxisTripleAndDistances) - # Skip when current axis is missing (i.e. doesn't participate), + # Skip when current axis is missing or peaks at 0 (i.e. doesn't participate) lower, peak, upper = var.axes.get(axisTag, (-1, 0, 1)) if peak == 0: + # explicitly defined, no-op axes can be omitted + # https://github.com/fonttools/fonttools/issues/3453 + if axisTag in var.axes: + del var.axes[axisTag] return [var] # Drop if the var 'tent' isn't well-formed if not (lower <= peak <= upper) or (lower < 0 and upper > 0): diff --git a/contrib/python/fonttools/ya.make b/contrib/python/fonttools/ya.make index e5986f9d03..a6872287a9 100644 --- a/contrib/python/fonttools/ya.make +++ b/contrib/python/fonttools/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(4.49.0) +VERSION(4.50.0) LICENSE(MIT) diff --git a/yt/yt/library/profiling/solomon/exporter.cpp b/yt/yt/library/profiling/solomon/exporter.cpp index 6cbb80632d..a7d2a356ec 100644 --- a/yt/yt/library/profiling/solomon/exporter.cpp +++ b/yt/yt/library/profiling/solomon/exporter.cpp @@ -118,6 +118,10 @@ void TSolomonExporterConfig::Register(TRegistrar registrar) registrar.Parameter("update_sensor_service_tree_period", &TThis::UpdateSensorServiceTreePeriod) .Default(TDuration::Seconds(30)); + registrar.Parameter("producer_collection_batch_size", &TThis::ProducerCollectionBatchSize) + .Default(DefaultProducerCollectionBatchSize) + .GreaterThan(0); + registrar.Postprocessor([] (TThis* config) { if (config->LingerTimeout.GetValue() % config->GridStep.GetValue() != 0) { THROW_ERROR_EXCEPTION("\"linger_timeout\" must be multiple of \"grid_step\""); @@ -208,6 +212,7 @@ TSolomonExporter::TSolomonExporter( } Registry_->SetWindowSize(Config_->WindowSize); + Registry_->SetProducerCollectionBatchSize(Config_->ProducerCollectionBatchSize); Registry_->SetGridFactor([config = Config_] (const TString& name) -> int { auto shard = config->MatchShard(name); if (!shard) { diff --git a/yt/yt/library/profiling/solomon/exporter.h b/yt/yt/library/profiling/solomon/exporter.h index 8aad2abcb1..1f2efdbd71 100644 --- a/yt/yt/library/profiling/solomon/exporter.h +++ b/yt/yt/library/profiling/solomon/exporter.h @@ -84,6 +84,8 @@ struct TSolomonExporterConfig TDuration UpdateSensorServiceTreePeriod; + int ProducerCollectionBatchSize; + TShardConfigPtr MatchShard(const TString& sensorName); ESummaryPolicy GetSummaryPolicy() const; diff --git a/yt/yt/library/profiling/solomon/private.h b/yt/yt/library/profiling/solomon/private.h index f011a0e471..82bb3c6b3b 100644 --- a/yt/yt/library/profiling/solomon/private.h +++ b/yt/yt/library/profiling/solomon/private.h @@ -8,6 +8,8 @@ namespace NYT::NProfiling { inline const NLogging::TLogger SolomonLogger("Solomon"); +inline const int DefaultProducerCollectionBatchSize = 100; + //////////////////////////////////////////////////////////////////////////////// } // namespace NYT::NProfiling diff --git a/yt/yt/library/profiling/solomon/producer.cpp b/yt/yt/library/profiling/solomon/producer.cpp index e54ed9e6ce..05072800b9 100644 --- a/yt/yt/library/profiling/solomon/producer.cpp +++ b/yt/yt/library/profiling/solomon/producer.cpp @@ -143,6 +143,58 @@ void TCounterWriter::AddCounter(const TString& name, i64 value) //////////////////////////////////////////////////////////////////////////////// +struct TOwningProducer +{ + ISensorProducerPtr Owner; + TProducerStatePtr Producer; +}; + +void DoCollectBatch( + const IRegistryImplPtr& profiler, + std::vector<TOwningProducer>&& batchArg, + const TEventTimer& collectDuration) +{ + auto batch = std::move(batchArg); + for (const auto& item : batch) { + TEventTimerGuard guard(collectDuration); + try { + const auto& producer = item.Producer; + const auto& buffer = item.Owner->GetBuffer(); + if (buffer) { + auto lastBuffer = producer->LastBuffer.Lock(); + if (lastBuffer == buffer) { + continue; + } + + TCounterWriter writer(profiler, producer->Counters, ++producer->LastUpdateIteration); + buffer->WriteTo(&writer); + producer->LastBuffer = buffer; + if (producer->Counters->Options.ProducerRemoveSupport) { + producer->Counters->ClearOutdated(producer->LastUpdateIteration); + } + } else { + producer->Counters->Counters.clear(); + producer->Counters->Gauges.clear(); + producer->Counters->Tags.clear(); + } + } catch (const std::exception& ex) { + YT_LOG_ERROR(ex, "Producer read failed"); + continue; + } + } +} + +TFuture<void> CollectBatchAsync( + const IInvokerPtr& invoker, + const IRegistryImplPtr& profiler, + std::vector<TOwningProducer>&& batch, + const TEventTimer& collectDuration) +{ + return BIND(&DoCollectBatch, profiler, Passed(std::move(batch)), collectDuration) + .AsyncVia(invoker) + .Run(); +} + void TProducerSet::AddProducer(TProducerStatePtr state) { Producers_.insert(std::move(state)); @@ -152,57 +204,51 @@ void TProducerSet::Collect(IRegistryImplPtr profiler, IInvokerPtr invoker) { std::vector<TFuture<void>> offloadFutures; std::deque<TProducerStatePtr> toRemove; + + std::vector<TOwningProducer> batch; + batch.reserve(BatchSize_); for (const auto& producer : Producers_) { auto owner = producer->Producer.Lock(); if (!owner) { toRemove.push_back(producer); - continue; + } else { + auto item = TOwningProducer{ + .Owner = std::move(owner), + .Producer = producer, + }; + batch.push_back(std::move(item)); } - auto future = BIND([profiler, owner, producer, collectDuration = ProducerCollectDuration_] () { - auto startTime = TInstant::Now(); - auto reportTime = Finally([&] { - collectDuration.Record(TInstant::Now() - startTime); - }); - - try { - auto buffer = owner->GetBuffer(); - if (buffer) { - auto lastBuffer = producer->LastBuffer.Lock(); - if (lastBuffer == buffer) { - return; - } - - TCounterWriter writer(profiler, producer->Counters, ++producer->LastUpdateIteration); - buffer->WriteTo(&writer); - producer->LastBuffer = buffer; - if (producer->Counters->Options.ProducerRemoveSupport) { - producer->Counters->ClearOutdated(producer->LastUpdateIteration); - } - } else { - producer->Counters->Counters.clear(); - producer->Counters->Gauges.clear(); - producer->Counters->Tags.clear(); - } - } catch (const std::exception& ex) { - YT_LOG_ERROR(ex, "Producer read failed"); - return; - } - }) - .AsyncVia(invoker) - .Run(); + if (std::ssize(batch) == BatchSize_) { + offloadFutures.push_back( + CollectBatchAsync( + invoker, + profiler, + std::move(batch), + ProducerCollectDuration_)); + batch.clear(); + } + } + if (!batch.empty()) { + offloadFutures.push_back( + CollectBatchAsync( + invoker, + profiler, + std::move(batch), + ProducerCollectDuration_)); + } + - offloadFutures.push_back(future); + for (const auto& producer : toRemove) { + Producers_.erase(producer); } + invoker->Invoke(BIND_NO_PROPAGATE([_ = std::move(toRemove)] { })); + // Use blocking Get(), because we want to lock current thread while data structure is updating. for (const auto& future : offloadFutures) { future.Get(); } - - for (const auto& producer : toRemove) { - Producers_.erase(producer); - } } void TProducerSet::Profile(const TProfiler& profiler) @@ -211,6 +257,11 @@ void TProducerSet::Profile(const TProfiler& profiler) ProducerCollectDuration_ = profiler.Timer("/producer_collect_duration"); } +void TProducerSet::SetCollectionBatchSize(int batchSize) +{ + BatchSize_ = batchSize; +} + //////////////////////////////////////////////////////////////////////////////// } // namespace NYT::NProfiling diff --git a/yt/yt/library/profiling/solomon/producer.h b/yt/yt/library/profiling/solomon/producer.h index 188309d68e..55a4c2cdf7 100644 --- a/yt/yt/library/profiling/solomon/producer.h +++ b/yt/yt/library/profiling/solomon/producer.h @@ -84,11 +84,15 @@ public: void Profile(const TProfiler& profiler); + void SetCollectionBatchSize(int batchSize); + private: THashSet<TProducerStatePtr> Producers_; TProfiler SelfProfiler_; TEventTimer ProducerCollectDuration_; + + int BatchSize_ = DefaultProducerCollectionBatchSize; }; //////////////////////////////////////////////////////////////////////////////// diff --git a/yt/yt/library/profiling/solomon/registry.cpp b/yt/yt/library/profiling/solomon/registry.cpp index b231a64782..18c8ba61cf 100644 --- a/yt/yt/library/profiling/solomon/registry.cpp +++ b/yt/yt/library/profiling/solomon/registry.cpp @@ -276,6 +276,11 @@ void TSolomonRegistry::SetWindowSize(int windowSize) WindowSize_ = windowSize; } +void TSolomonRegistry::SetProducerCollectionBatchSize(int batchSize) +{ + Producers_.SetCollectionBatchSize(batchSize); +} + int TSolomonRegistry::GetWindowSize() const { if (!WindowSize_) { diff --git a/yt/yt/library/profiling/solomon/registry.h b/yt/yt/library/profiling/solomon/registry.h index 0569f18cd9..5b8174db02 100644 --- a/yt/yt/library/profiling/solomon/registry.h +++ b/yt/yt/library/profiling/solomon/registry.h @@ -128,6 +128,7 @@ public: void SetGridFactor(std::function<int(const TString&)> gridFactor); void SetWindowSize(int windowSize); + void SetProducerCollectionBatchSize(int batchSize); void ProcessRegistrations(); void Collect(IInvokerPtr offloadInvoker = GetSyncInvoker()); void ReadSensors( |