< Summary

Information
Class: Ice.Internal.MetricsMap<T>
Assembly: Ice
File(s): /_/csharp/src/Ice/Internal/MetricsAdminI.cs
Tag: 91_21789722663
Line coverage
78%
Covered lines: 105
Uncovered lines: 28
Coverable lines: 133
Total lines: 976
Line coverage: 78.9%
Branch coverage
72%
Covered branches: 58
Total branches: 80
Branch coverage: 72.5%
Method coverage
90%
Covered methods: 10
Total methods: 11
Method coverage: 90.9%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)73.53%41.443481.4%
.ctor(...)100%11100%
getProperties()100%11100%
getMetrics()100%22100%
getFailures()0%2040%
getFailures(...)50%22100%
createSubMap(...)50%5.02460%
getMatching(...)75%28.892071.88%
parseRule(...)100%22100%
match(...)100%11100%
detached(...)91.67%12.051292.86%

File(s)

/_/csharp/src/Ice/Internal/MetricsAdminI.cs

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3using System.Diagnostics;
 4using System.Text;
 5using System.Text.RegularExpressions;
 6
 7namespace Ice.Internal;
 8
 9internal interface IMetricsMap
 10{
 11    IceMX.Metrics[] getMetrics();
 12
 13    IceMX.MetricsFailures[] getFailures();
 14
 15    IceMX.MetricsFailures getFailures(string id);
 16
 17    Dictionary<string, string> getProperties();
 18}
 19
 20internal interface ISubMap
 21{
 22    void addSubMapToMetrics(IceMX.Metrics metrics);
 23}
 24
 25internal interface ISubMapCloneFactory
 26{
 27    ISubMap create();
 28}
 29
 30internal interface ISubMapFactory
 31{
 32    ISubMapCloneFactory createCloneFactory(string subMapPrefix, Ice.Properties properties);
 33}
 34
 35internal interface IMetricsMapFactory
 36{
 37    void registerSubMap<S>(string subMap, System.Reflection.FieldInfo field) where S : IceMX.Metrics, new();
 38
 39    void update();
 40
 41    IMetricsMap create(string mapPrefix, Ice.Properties properties);
 42}
 43
 44internal class SubMap<S> : ISubMap where S : IceMX.Metrics, new()
 45{
 46    internal SubMap(MetricsMap<S> map, System.Reflection.FieldInfo field)
 47    {
 48        _map = map;
 49        _field = field;
 50    }
 51
 52    internal MetricsMap<S>.Entry getMatching(IceMX.MetricsHelper<S> helper) => _map.getMatching(helper, null);
 53
 54    public void addSubMapToMetrics(IceMX.Metrics metrics)
 55    {
 56        try
 57        {
 58            _field.SetValue(metrics, _map.getMetrics());
 59        }
 60        catch (System.Exception)
 61        {
 62            Debug.Assert(false);
 63        }
 64    }
 65
 66    private readonly MetricsMap<S> _map;
 67    private readonly System.Reflection.FieldInfo _field;
 68}
 69
 70internal class SubMapCloneFactory<S> : ISubMapCloneFactory where S : IceMX.Metrics, new()
 71{
 72    internal SubMapCloneFactory(MetricsMap<S> map, System.Reflection.FieldInfo field)
 73    {
 74        _map = map;
 75        _field = field;
 76    }
 77
 78    public ISubMap create() => new SubMap<S>(new MetricsMap<S>(_map), _field);
 79
 80    private readonly MetricsMap<S> _map;
 81    private readonly System.Reflection.FieldInfo _field;
 82}
 83
 84internal class SubMapFactory<S> : ISubMapFactory where S : IceMX.Metrics, new()
 85{
 86    internal SubMapFactory(System.Reflection.FieldInfo field) => _field = field;
 87
 88    public ISubMapCloneFactory createCloneFactory(
 89        string subMapPrefix,
 90        Ice.Properties properties) =>
 91        new SubMapCloneFactory<S>(new MetricsMap<S>(subMapPrefix, properties, null), _field);
 92
 93    private readonly System.Reflection.FieldInfo _field;
 94}
 95
 96public class MetricsMap<T> : IMetricsMap where T : IceMX.Metrics, new()
 97{
 98    public class Entry
 99    {
 100        internal Entry(MetricsMap<T> map, T obj)
 101        {
 102            _map = map;
 103            _object = obj;
 104        }
 105
 106        public void failed(string exceptionName)
 107        {
 108            lock (_map._mutex)
 109            {
 110                ++_object.failures;
 111                _failures ??= new Dictionary<string, int>();
 112                if (_failures.TryGetValue(exceptionName, out int count))
 113                {
 114                    _failures[exceptionName] = count + 1;
 115                }
 116                else
 117                {
 118                    _failures[exceptionName] = 1;
 119                }
 120            }
 121        }
 122
 123        internal MetricsMap<S>.Entry getMatching<S>(string mapName, IceMX.MetricsHelper<S> helper)
 124            where S : IceMX.Metrics, new()
 125        {
 126            ISubMap m;
 127            lock (_map._mutex)
 128            {
 129                if (_subMaps == null || !_subMaps.TryGetValue(mapName, out m))
 130                {
 131                    m = _map.createSubMap(mapName);
 132                    if (m == null)
 133                    {
 134                        return null;
 135                    }
 136                    _subMaps ??= new Dictionary<string, ISubMap>();
 137                    _subMaps.Add(mapName, m);
 138                }
 139            }
 140            return ((SubMap<S>)m).getMatching(helper);
 141        }
 142
 143        public void detach(long lifetime)
 144        {
 145            lock (_map._mutex)
 146            {
 147                _object.totalLifetime += lifetime;
 148                if (--_object.current == 0)
 149                {
 150                    _map.detached(this);
 151                }
 152            }
 153        }
 154
 155        public void execute(IceMX.Observer<T>.MetricsUpdate func)
 156        {
 157            lock (_map)
 158            {
 159                func(_object);
 160            }
 161        }
 162
 163        public MetricsMap<T> getMap() => _map;
 164
 165        internal IceMX.MetricsFailures getFailures()
 166        {
 167            if (_failures == null)
 168            {
 169                return new(failures: []);
 170            }
 171            return new(_object.id, new Dictionary<string, int>(_failures));
 172        }
 173
 174        internal void attach(IceMX.MetricsHelper<T> helper)
 175        {
 176            ++_object.total;
 177            ++_object.current;
 178            helper.initMetrics(_object);
 179        }
 180
 181        internal bool isDetached() => _object.current == 0;
 182
 183        internal IceMX.Metrics clone()
 184        {
 185            var metrics = (T)_object.Clone();
 186            if (_subMaps != null)
 187            {
 188                foreach (ISubMap s in _subMaps.Values)
 189                {
 190                    s.addSubMapToMetrics(metrics);
 191                }
 192            }
 193            return metrics;
 194        }
 195
 196        internal string getId() => _object.id;
 197
 198        private readonly MetricsMap<T> _map;
 199        private readonly T _object;
 200        private Dictionary<string, int> _failures;
 201        private Dictionary<string, ISubMap> _subMaps;
 202    }
 203
 1204    internal MetricsMap(string mapPrefix, Ice.Properties props, Dictionary<string, ISubMapFactory> subMaps)
 205    {
 1206        MetricsAdminI.validateProperties(mapPrefix, props);
 1207        _properties = props.getPropertiesForPrefix(mapPrefix);
 208
 1209        _retain = props.getPropertyAsIntWithDefault(mapPrefix + "RetainDetached", 10);
 1210        _accept = parseRule(props, mapPrefix + "Accept");
 1211        _reject = parseRule(props, mapPrefix + "Reject");
 1212        _groupByAttributes = new List<string>();
 1213        _groupBySeparators = new List<string>();
 214
 1215        string groupBy = props.getPropertyWithDefault(mapPrefix + "GroupBy", "id");
 1216        if (groupBy.Length > 0)
 217        {
 1218            string v = "";
 1219            bool attribute = char.IsLetter(groupBy[0]) || char.IsDigit(groupBy[0]);
 1220            if (!attribute)
 221            {
 0222                _groupByAttributes.Add("");
 223            }
 224
 1225            foreach (char p in groupBy)
 226            {
 1227                bool isAlphaNum = char.IsLetter(p) || char.IsDigit(p) || p == '.';
 1228                if (attribute && !isAlphaNum)
 229                {
 0230                    _groupByAttributes.Add(v);
 0231                    v = "" + p;
 0232                    attribute = false;
 233                }
 1234                else if (!attribute && isAlphaNum)
 235                {
 0236                    _groupBySeparators.Add(v);
 0237                    v = "" + p;
 0238                    attribute = true;
 239                }
 240                else
 241                {
 1242                    v += p;
 243                }
 244            }
 245
 1246            if (attribute)
 247            {
 1248                _groupByAttributes.Add(v);
 249            }
 250            else
 251            {
 0252                _groupBySeparators.Add(v);
 253            }
 254        }
 255
 1256        if (subMaps != null && subMaps.Count > 0)
 257        {
 1258            _subMaps = new Dictionary<string, ISubMapCloneFactory>();
 259
 1260            var subMapNames = new List<string>();
 1261            foreach (KeyValuePair<string, ISubMapFactory> e in subMaps)
 262            {
 1263                subMapNames.Add(e.Key);
 1264                string subMapsPrefix = mapPrefix + "Map.";
 1265                string subMapPrefix = subMapsPrefix + e.Key + '.';
 1266                if (props.getPropertiesForPrefix(subMapPrefix).Count == 0)
 267                {
 1268                    if (props.getPropertiesForPrefix(subMapsPrefix).Count == 0)
 269                    {
 1270                        subMapPrefix = mapPrefix;
 271                    }
 272                    else
 273                    {
 274                        continue; // This sub-map isn't configured.
 275                    }
 276                }
 277
 1278                _subMaps.Add(e.Key, e.Value.createCloneFactory(subMapPrefix, props));
 279            }
 280        }
 281        else
 282        {
 1283            _subMaps = null;
 284        }
 1285    }
 286
 1287    internal MetricsMap(MetricsMap<T> map)
 288    {
 1289        _properties = map._properties;
 1290        _groupByAttributes = map._groupByAttributes;
 1291        _groupBySeparators = map._groupBySeparators;
 1292        _retain = map._retain;
 1293        _accept = map._accept;
 1294        _reject = map._reject;
 1295        _subMaps = map._subMaps;
 1296    }
 297
 1298    public Dictionary<string, string> getProperties() => _properties;
 299
 300    public IceMX.Metrics[] getMetrics()
 301    {
 1302        lock (_mutex)
 303        {
 1304            var metrics = new IceMX.Metrics[_objects.Count];
 1305            int i = 0;
 1306            foreach (Entry e in _objects.Values)
 307            {
 1308                metrics[i++] = e.clone();
 309            }
 1310            return metrics;
 311        }
 1312    }
 313
 314    public IceMX.MetricsFailures[] getFailures()
 315    {
 0316        lock (_mutex)
 317        {
 0318            var failures = new List<IceMX.MetricsFailures>();
 0319            foreach (Entry e in _objects.Values)
 320            {
 0321                IceMX.MetricsFailures f = e.getFailures();
 0322                if (f != null)
 323                {
 0324                    failures.Add(f);
 325                }
 326            }
 0327            return failures.ToArray();
 328        }
 0329    }
 330
 331    public IceMX.MetricsFailures getFailures(string id)
 332    {
 1333        lock (_mutex)
 334        {
 1335            return _objects.TryGetValue(id, out Entry e) ? e.getFailures() : new(failures: []);
 336        }
 1337    }
 338
 339    private ISubMap createSubMap(string subMapName)
 340    {
 1341        if (_subMaps == null)
 342        {
 0343            return null;
 344        }
 1345        if (_subMaps.TryGetValue(subMapName, out ISubMapCloneFactory factory))
 346        {
 1347            return factory.create();
 348        }
 0349        return null;
 350    }
 351
 352    public Entry getMatching(IceMX.MetricsHelper<T> helper, Entry previous)
 353    {
 354        //
 355        // Check the accept and reject filters.
 356        //
 1357        foreach (KeyValuePair<string, Regex> e in _accept)
 358        {
 1359            if (!match(e.Key, e.Value, helper, false))
 360            {
 1361                return null;
 362            }
 363        }
 364
 1365        foreach (KeyValuePair<string, Regex> e in _reject)
 366        {
 1367            if (match(e.Key, e.Value, helper, true))
 368            {
 1369                return null;
 370            }
 371        }
 372
 373        //
 374        // Compute the key from the GroupBy property.
 375        //
 376        string key;
 377        try
 378        {
 1379            if (_groupByAttributes.Count == 1)
 380            {
 1381                key = helper.resolve(_groupByAttributes[0]);
 382            }
 383            else
 384            {
 0385                var os = new StringBuilder();
 0386                IEnumerator<string> q = _groupBySeparators.GetEnumerator();
 0387                foreach (string p in _groupByAttributes)
 388                {
 0389                    os.Append(helper.resolve(p));
 0390                    if (q.MoveNext())
 391                    {
 0392                        os.Append(q.Current);
 393                    }
 394                }
 0395                key = os.ToString();
 396            }
 1397        }
 1398        catch (System.Exception)
 399        {
 1400            return null;
 401        }
 402
 403        //
 404        // Lookup the metrics object.
 405        //
 1406        lock (_mutex)
 407        {
 1408            if (previous != null && previous.getId().Equals(key, StringComparison.Ordinal))
 409            {
 410                Debug.Assert(_objects[key] == previous);
 1411                return previous;
 412            }
 413
 1414            if (!_objects.TryGetValue(key, out Entry e))
 415            {
 416                try
 417                {
 1418                    var t = new T();
 1419                    t.id = key;
 1420                    e = new Entry(this, t);
 1421                    _objects.Add(key, e);
 1422                }
 0423                catch (System.Exception)
 424                {
 425                    Debug.Assert(false);
 0426                }
 427            }
 1428            e.attach(helper);
 1429            return e;
 430        }
 1431    }
 432
 433    private static Dictionary<string, Regex> parseRule(Ice.Properties properties, string name)
 434    {
 1435        var pats = new Dictionary<string, Regex>();
 1436        Dictionary<string, string> rules = properties.getPropertiesForPrefix(name + '.');
 1437        foreach (KeyValuePair<string, string> e in rules)
 438        {
 1439            pats.Add(e.Key[(name.Length + 1)..], new Regex(e.Value));
 440        }
 1441        return pats;
 442    }
 443
 444    private static bool match(string attribute, Regex regex, IceMX.MetricsHelper<T> helper, bool reject)
 445    {
 446        string value;
 447        try
 448        {
 1449            value = helper.resolve(attribute);
 1450        }
 1451        catch (System.Exception)
 452        {
 1453            return !reject;
 454        }
 1455        return regex.IsMatch(value);
 1456    }
 457
 458    private void detached(Entry entry)
 459    {
 1460        if (_retain == 0)
 461        {
 0462            return;
 463        }
 464
 1465        _detachedQueue ??= new LinkedList<Entry>();
 466        Debug.Assert(_detachedQueue.Count <= _retain);
 467
 468        // Compress the queue by removing entries which are no longer detached.
 1469        LinkedListNode<Entry> p = _detachedQueue.First;
 1470        while (p != null)
 471        {
 1472            LinkedListNode<Entry> next = p.Next;
 1473            if (p.Value == entry || !p.Value.isDetached())
 474            {
 1475                _detachedQueue.Remove(p);
 476            }
 1477            p = next;
 478        }
 479
 480        // If there's still no room, remove the oldest entry (at the front).
 1481        if (_detachedQueue.Count == _retain)
 482        {
 1483            _objects.Remove(_detachedQueue.First.Value.getId());
 1484            _detachedQueue.RemoveFirst();
 485        }
 486
 487        // Add the entry at the back of the queue.
 1488        _detachedQueue.AddLast(entry);
 1489    }
 490
 1491    internal readonly object _mutex = new();
 492    private readonly Dictionary<string, string> _properties;
 493    private readonly List<string> _groupByAttributes;
 494    private readonly List<string> _groupBySeparators;
 495    private readonly int _retain;
 496    private readonly Dictionary<string, Regex> _accept;
 497    private readonly Dictionary<string, Regex> _reject;
 498
 1499    private readonly Dictionary<string, Entry> _objects = new();
 500    private readonly Dictionary<string, ISubMapCloneFactory> _subMaps;
 501    private LinkedList<Entry> _detachedQueue;
 502}
 503
 504internal class MetricsViewI
 505{
 506    internal MetricsViewI(string name) => _name = name;
 507
 508    internal bool addOrUpdateMap(
 509        Ice.Properties properties,
 510        string mapName,
 511        IMetricsMapFactory factory,
 512        Ice.Logger logger)
 513    {
 514        //
 515        // Add maps to views configured with the given map.
 516        //
 517        string viewPrefix = "IceMX.Metrics." + _name + ".";
 518        string mapsPrefix = viewPrefix + "Map.";
 519        Dictionary<string, string> mapsProps = properties.getPropertiesForPrefix(mapsPrefix);
 520
 521        string mapPrefix;
 522        _ = new Dictionary<string, string>();
 523        Dictionary<string, string> mapProps;
 524        if (mapsProps.Count > 0)
 525        {
 526            mapPrefix = mapsPrefix + mapName + ".";
 527            mapProps = properties.getPropertiesForPrefix(mapPrefix);
 528            if (mapProps.Count == 0)
 529            {
 530                // This map isn't configured for this view.
 531                return _maps.Remove(mapName);
 532            }
 533        }
 534        else
 535        {
 536            mapPrefix = viewPrefix;
 537            mapProps = properties.getPropertiesForPrefix(mapPrefix);
 538        }
 539
 540        if (properties.getPropertyAsInt(mapPrefix + "Disabled") > 0)
 541        {
 542            // This map is disabled for this view.
 543            return _maps.Remove(mapName);
 544        }
 545
 546        if (_maps.TryGetValue(mapName, out IMetricsMap m) && m.getProperties().DictionaryEqual(mapProps))
 547        {
 548            return false; // The map configuration didn't change, no need to re-create.
 549        }
 550
 551        try
 552        {
 553            _maps[mapName] = factory.create(mapPrefix, properties);
 554        }
 555        catch (System.Exception ex)
 556        {
 557            logger.warning("unexpected exception while creating metrics map:\n" + ex);
 558            _maps.Remove(mapName);
 559        }
 560        return true;
 561    }
 562
 563    internal bool removeMap(string mapName) => _maps.Remove(mapName);
 564
 565    internal Dictionary<string, IceMX.Metrics[]> getMetrics()
 566    {
 567        var metrics = new Dictionary<string, IceMX.Metrics[]>();
 568        foreach (KeyValuePair<string, IMetricsMap> e in _maps)
 569        {
 570            IceMX.Metrics[] m = e.Value.getMetrics();
 571            if (m != null)
 572            {
 573                metrics.Add(e.Key, m);
 574            }
 575        }
 576        return metrics;
 577    }
 578
 579    internal IceMX.MetricsFailures[] getFailures(string mapName)
 580    {
 581        if (_maps.TryGetValue(mapName, out IMetricsMap m))
 582        {
 583            return m.getFailures();
 584        }
 585        return [];
 586    }
 587
 588    internal IceMX.MetricsFailures getFailures(string mapName, string id)
 589    {
 590        if (_maps.TryGetValue(mapName, out IMetricsMap m))
 591        {
 592            return m.getFailures(id);
 593        }
 594        return new(failures: []);
 595    }
 596
 597    internal ICollection<string> getMaps() => _maps.Keys;
 598
 599    internal MetricsMap<T> getMap<T>(string mapName) where T : IceMX.Metrics, new()
 600    {
 601        if (_maps.TryGetValue(mapName, out IMetricsMap m))
 602        {
 603            return (MetricsMap<T>)m;
 604        }
 605        return null;
 606    }
 607
 608    private readonly string _name;
 609    private readonly Dictionary<string, IMetricsMap> _maps = new Dictionary<string, IMetricsMap>();
 610}
 611
 612public class MetricsAdminI : IceMX.MetricsAdminDisp_
 613{
 614    private static readonly string[] suffixes =
 615        {
 616            "Disabled",
 617            "GroupBy",
 618            "Accept.*",
 619            "Reject.*",
 620            "RetainDetached",
 621            "Map.*",
 622        };
 623
 624    public static void validateProperties(string prefix, Ice.Properties properties)
 625    {
 626        Dictionary<string, string> props = properties.getPropertiesForPrefix(prefix);
 627        var unknownProps = new List<string>();
 628        foreach (string prop in props.Keys)
 629        {
 630            bool valid = false;
 631            foreach (string suffix in suffixes)
 632            {
 633                if (Ice.UtilInternal.StringUtil.match(prop, prefix + suffix, false))
 634                {
 635                    valid = true;
 636                    break;
 637                }
 638            }
 639
 640            if (!valid)
 641            {
 642                unknownProps.Add(prop);
 643            }
 644        }
 645
 646        if (unknownProps.Count != 0)
 647        {
 648            var message = new StringBuilder("Found unknown IceMX properties for '");
 649            message.Append(prefix.AsSpan(0, prefix.Length - 1));
 650            message.Append("':");
 651            foreach (string p in unknownProps)
 652            {
 653                message.Append("\n    ");
 654                message.Append(p);
 655            }
 656            throw new PropertyException(message.ToString());
 657        }
 658    }
 659
 660    private class MetricsMapFactory<T> : IMetricsMapFactory where T : IceMX.Metrics, new()
 661    {
 662        public MetricsMapFactory(Action updater) => _updater = updater;
 663
 664        public void update()
 665        {
 666            Debug.Assert(_updater != null);
 667            _updater();
 668        }
 669
 670        public IMetricsMap create(string mapPrefix, Ice.Properties properties) =>
 671            new MetricsMap<T>(mapPrefix, properties, _subMaps);
 672
 673        public void registerSubMap<S>(string subMap, System.Reflection.FieldInfo field)
 674            where S : IceMX.Metrics, new() => _subMaps.Add(subMap, new SubMapFactory<S>(field));
 675
 676        private readonly Action _updater;
 677        private readonly Dictionary<string, ISubMapFactory> _subMaps = new Dictionary<string, ISubMapFactory>();
 678    }
 679
 680    public MetricsAdminI(Ice.Properties properties, Ice.Logger logger)
 681    {
 682        _logger = logger;
 683        _properties = properties;
 684        updateViews();
 685    }
 686
 687    public void updateViews()
 688    {
 689        var updatedMaps = new HashSet<IMetricsMapFactory>();
 690        lock (_mutex)
 691        {
 692            string viewsPrefix = "IceMX.Metrics.";
 693            Dictionary<string, string> viewsProps = _properties.getPropertiesForPrefix(viewsPrefix);
 694            var views = new Dictionary<string, MetricsViewI>();
 695            _disabledViews.Clear();
 696            foreach (KeyValuePair<string, string> e in viewsProps)
 697            {
 698                string viewName = e.Key[viewsPrefix.Length..];
 699                int dotPos = viewName.IndexOf('.', StringComparison.Ordinal);
 700                if (dotPos > 0)
 701                {
 702                    viewName = viewName[..dotPos];
 703                }
 704
 705                if (views.ContainsKey(viewName) || _disabledViews.Contains(viewName))
 706                {
 707                    continue; // View already configured.
 708                }
 709
 710                validateProperties(viewsPrefix + viewName + ".", _properties);
 711
 712                if (_properties.getPropertyAsIntWithDefault(viewsPrefix + viewName + ".Disabled", 0) > 0)
 713                {
 714                    _disabledViews.Add(viewName);
 715                    continue; // The view is disabled
 716                }
 717
 718                //
 719                // Create the view or update it.
 720                //
 721                if (!_views.TryGetValue(viewName, out MetricsViewI v))
 722                {
 723                    v = new MetricsViewI(viewName);
 724                }
 725                views[viewName] = v;
 726
 727                foreach (KeyValuePair<string, IMetricsMapFactory> f in _factories)
 728                {
 729                    if (v.addOrUpdateMap(_properties, f.Key, f.Value, _logger))
 730                    {
 731                        updatedMaps.Add(f.Value);
 732                    }
 733                }
 734            }
 735
 736            Dictionary<string, MetricsViewI> tmp = _views;
 737            _views = views;
 738            views = tmp;
 739
 740            //
 741            // Go through removed views to collect maps to update.
 742            //
 743            foreach (KeyValuePair<string, MetricsViewI> v in views)
 744            {
 745                if (!_views.ContainsKey(v.Key))
 746                {
 747                    foreach (string n in v.Value.getMaps())
 748                    {
 749                        updatedMaps.Add(_factories[n]);
 750                    }
 751                }
 752            }
 753        }
 754
 755        //
 756        // Call the updaters to update the maps.
 757        //
 758        foreach (IMetricsMapFactory f in updatedMaps)
 759        {
 760            f.update();
 761        }
 762    }
 763
 764    public override string[] getMetricsViewNames(out string[] disabledViews, Ice.Current current)
 765    {
 766        lock (_mutex)
 767        {
 768            disabledViews = _disabledViews.ToArray();
 769            return new List<string>(_views.Keys).ToArray();
 770        }
 771    }
 772
 773    public override void enableMetricsView(string name, Ice.Current current)
 774    {
 775        lock (_mutex)
 776        {
 777            getMetricsView(name); // Throws if unknown view.
 778            _properties.setProperty("IceMX.Metrics." + name + ".Disabled", "0");
 779        }
 780        updateViews();
 781    }
 782
 783    public override void disableMetricsView(string name, Ice.Current current)
 784    {
 785        lock (_mutex)
 786        {
 787            getMetricsView(name); // Throws if unknown view.
 788            _properties.setProperty("IceMX.Metrics." + name + ".Disabled", "1");
 789        }
 790        updateViews();
 791    }
 792
 793    public override Dictionary<string, IceMX.Metrics[]>
 794    getMetricsView(string view, out long timestamp, Ice.Current current)
 795    {
 796        lock (_mutex)
 797        {
 798            MetricsViewI viewI = getMetricsView(view);
 799            timestamp = Time.currentMonotonicTimeMillis();
 800            if (viewI is not null)
 801            {
 802                return viewI.getMetrics();
 803            }
 804            return new Dictionary<string, IceMX.Metrics[]>();
 805        }
 806    }
 807
 808    public override IceMX.MetricsFailures[] getMapMetricsFailures(string view, string map, Ice.Current current)
 809    {
 810        lock (_mutex)
 811        {
 812            MetricsViewI viewI = getMetricsView(view);
 813            if (viewI is not null)
 814            {
 815                return viewI.getFailures(map);
 816            }
 817            return [];
 818        }
 819    }
 820
 821    public override IceMX.MetricsFailures getMetricsFailures(
 822        string view,
 823        string map,
 824        string id,
 825        Ice.Current current)
 826    {
 827        lock (_mutex)
 828        {
 829            MetricsViewI viewI = getMetricsView(view);
 830            if (viewI is not null)
 831            {
 832                return viewI.getFailures(map, id);
 833            }
 834            return new(failures: []);
 835        }
 836    }
 837
 838    public void registerMap<T>(string map, Action updater)
 839        where T : IceMX.Metrics, new()
 840    {
 841        bool updated;
 842        MetricsMapFactory<T> factory;
 843        lock (_mutex)
 844        {
 845            factory = new MetricsMapFactory<T>(updater);
 846            _factories.Add(map, factory);
 847            updated = addOrUpdateMap(map, factory);
 848        }
 849        if (updated)
 850        {
 851            factory.update();
 852        }
 853    }
 854
 855    public void registerSubMap<S>(string map, string subMap, System.Reflection.FieldInfo field)
 856        where S : IceMX.Metrics, new()
 857    {
 858        bool updated;
 859        IMetricsMapFactory factory;
 860        lock (_mutex)
 861        {
 862            if (!_factories.TryGetValue(map, out factory))
 863            {
 864                return;
 865            }
 866            factory.registerSubMap<S>(subMap, field);
 867            removeMap(map);
 868            updated = addOrUpdateMap(map, factory);
 869        }
 870        if (updated)
 871        {
 872            factory.update();
 873        }
 874    }
 875
 876    public void unregisterMap(string mapName)
 877    {
 878        bool updated;
 879        IMetricsMapFactory factory;
 880        lock (_mutex)
 881        {
 882            if (!_factories.TryGetValue(mapName, out factory))
 883            {
 884                return;
 885            }
 886            _factories.Remove(mapName);
 887            updated = removeMap(mapName);
 888        }
 889        if (updated)
 890        {
 891            factory.update();
 892        }
 893    }
 894
 895    public List<MetricsMap<T>> getMaps<T>(string mapName) where T : IceMX.Metrics, new()
 896    {
 897        var maps = new List<MetricsMap<T>>();
 898        foreach (MetricsViewI v in _views.Values)
 899        {
 900            MetricsMap<T> map = v.getMap<T>(mapName);
 901            if (map != null)
 902            {
 903                maps.Add(map);
 904            }
 905        }
 906        return maps;
 907    }
 908
 909    public Ice.Logger getLogger() => _logger;
 910
 911    internal void updated(Dictionary<string, string> changes)
 912    {
 913        foreach (KeyValuePair<string, string> e in changes)
 914        {
 915            if (e.Key.StartsWith("IceMX.", StringComparison.Ordinal))
 916            {
 917                // Update the metrics views using the new configuration.
 918                try
 919                {
 920                    updateViews();
 921                }
 922                catch (System.Exception ex)
 923                {
 924                    _logger.warning("unexpected exception while updating metrics view configuration:\n" +
 925                                    ex.ToString());
 926                }
 927                return;
 928            }
 929        }
 930    }
 931
 932    private MetricsViewI getMetricsView(string name)
 933    {
 934        if (!_views.TryGetValue(name, out MetricsViewI view))
 935        {
 936            if (!_disabledViews.Contains(name))
 937            {
 938                throw new IceMX.UnknownMetricsView();
 939            }
 940            return null;
 941        }
 942        return view;
 943    }
 944
 945    private bool addOrUpdateMap(string mapName, IMetricsMapFactory factory)
 946    {
 947        bool updated = false;
 948        foreach (MetricsViewI v in _views.Values)
 949        {
 950            updated |= v.addOrUpdateMap(_properties, mapName, factory, _logger);
 951        }
 952        return updated;
 953    }
 954
 955    private bool removeMap(string mapName)
 956    {
 957        bool updated = false;
 958        foreach (MetricsViewI v in _views.Values)
 959        {
 960            updated |= v.removeMap(mapName);
 961        }
 962        return updated;
 963    }
 964
 965    private readonly Ice.Properties _properties;
 966
 967    private readonly Ice.Logger _logger;
 968
 969    private readonly Dictionary<string, IMetricsMapFactory> _factories =
 970        new Dictionary<string, IMetricsMapFactory>();
 971
 972    private Dictionary<string, MetricsViewI> _views = new Dictionary<string, MetricsViewI>();
 973
 974    private readonly List<string> _disabledViews = new List<string>();
 975    private readonly object _mutex = new();
 976}