< Summary

Information
Class: Ice.Internal.SubMap<S>
Assembly: Ice
File(s): /_/csharp/src/Ice/Internal/MetricsAdminI.cs
Tag: 125_37167941578
Line coverage
80%
Covered lines: 8
Uncovered lines: 2
Coverable lines: 10
Total lines: 980
Line coverage: 80%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage
100%
Covered methods: 3
Fully covered methods: 2
Total methods: 3
Method coverage: 100%
Full method coverage: 66.6%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
getMatching(...)100%11100%
addSubMapToMetrics(...)100%1160%

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{
 146    internal SubMap(MetricsMap<S> map, System.Reflection.FieldInfo field)
 47    {
 148        _map = map;
 149        _field = field;
 150    }
 51
 152    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        {
 158            _field.SetValue(metrics, _map.getMetrics());
 159        }
 060        catch (System.Exception)
 61        {
 62            Debug.Assert(false);
 063        }
 164    }
 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._mutex)
 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
 204    internal MetricsMap(string mapPrefix, Ice.Properties props, Dictionary<string, ISubMapFactory> subMaps)
 205    {
 206        MetricsAdminI.validateProperties(mapPrefix, props);
 207        _properties = props.getPropertiesForPrefix(mapPrefix);
 208
 209        _retain = props.getPropertyAsIntWithDefault(mapPrefix + "RetainDetached", 10);
 210        _accept = parseRule(props, mapPrefix + "Accept");
 211        _reject = parseRule(props, mapPrefix + "Reject");
 212        _groupByAttributes = new List<string>();
 213        _groupBySeparators = new List<string>();
 214
 215        string groupBy = props.getPropertyWithDefault(mapPrefix + "GroupBy", "id");
 216        if (groupBy.Length > 0)
 217        {
 218            string v = "";
 219            bool attribute = char.IsLetter(groupBy[0]) || char.IsDigit(groupBy[0]);
 220            if (!attribute)
 221            {
 222                _groupByAttributes.Add("");
 223            }
 224
 225            foreach (char p in groupBy)
 226            {
 227                bool isAlphaNum = char.IsLetter(p) || char.IsDigit(p) || p == '.';
 228                if (attribute && !isAlphaNum)
 229                {
 230                    _groupByAttributes.Add(v);
 231                    v = "" + p;
 232                    attribute = false;
 233                }
 234                else if (!attribute && isAlphaNum)
 235                {
 236                    _groupBySeparators.Add(v);
 237                    v = "" + p;
 238                    attribute = true;
 239                }
 240                else
 241                {
 242                    v += p;
 243                }
 244            }
 245
 246            if (attribute)
 247            {
 248                _groupByAttributes.Add(v);
 249            }
 250            else
 251            {
 252                _groupBySeparators.Add(v);
 253            }
 254        }
 255
 256        if (subMaps != null && subMaps.Count > 0)
 257        {
 258            _subMaps = new Dictionary<string, ISubMapCloneFactory>();
 259
 260            var subMapNames = new List<string>();
 261            foreach (KeyValuePair<string, ISubMapFactory> e in subMaps)
 262            {
 263                subMapNames.Add(e.Key);
 264                string subMapsPrefix = mapPrefix + "Map.";
 265                string subMapPrefix = subMapsPrefix + e.Key + '.';
 266                if (props.getPropertiesForPrefix(subMapPrefix).Count == 0)
 267                {
 268                    if (props.getPropertiesForPrefix(subMapsPrefix).Count == 0)
 269                    {
 270                        subMapPrefix = mapPrefix;
 271                    }
 272                    else
 273                    {
 274                        continue; // This sub-map isn't configured.
 275                    }
 276                }
 277
 278                _subMaps.Add(e.Key, e.Value.createCloneFactory(subMapPrefix, props));
 279            }
 280        }
 281        else
 282        {
 283            _subMaps = null;
 284        }
 285    }
 286
 287    internal MetricsMap(MetricsMap<T> map)
 288    {
 289        _properties = map._properties;
 290        _groupByAttributes = map._groupByAttributes;
 291        _groupBySeparators = map._groupBySeparators;
 292        _retain = map._retain;
 293        _accept = map._accept;
 294        _reject = map._reject;
 295        _subMaps = map._subMaps;
 296    }
 297
 298    public Dictionary<string, string> getProperties() => _properties;
 299
 300    public IceMX.Metrics[] getMetrics()
 301    {
 302        lock (_mutex)
 303        {
 304            var metrics = new IceMX.Metrics[_objects.Count];
 305            int i = 0;
 306            foreach (Entry e in _objects.Values)
 307            {
 308                metrics[i++] = e.clone();
 309            }
 310            return metrics;
 311        }
 312    }
 313
 314    public IceMX.MetricsFailures[] getFailures()
 315    {
 316        lock (_mutex)
 317        {
 318            var failures = new List<IceMX.MetricsFailures>();
 319            foreach (Entry e in _objects.Values)
 320            {
 321                IceMX.MetricsFailures f = e.getFailures();
 322                if (f != null)
 323                {
 324                    failures.Add(f);
 325                }
 326            }
 327            return failures.ToArray();
 328        }
 329    }
 330
 331    public IceMX.MetricsFailures getFailures(string id)
 332    {
 333        lock (_mutex)
 334        {
 335            return _objects.TryGetValue(id, out Entry e) ? e.getFailures() : new(failures: []);
 336        }
 337    }
 338
 339    private ISubMap createSubMap(string subMapName)
 340    {
 341        if (_subMaps == null)
 342        {
 343            return null;
 344        }
 345        if (_subMaps.TryGetValue(subMapName, out ISubMapCloneFactory factory))
 346        {
 347            return factory.create();
 348        }
 349        return null;
 350    }
 351
 352    public Entry getMatching(IceMX.MetricsHelper<T> helper, Entry previous)
 353    {
 354        //
 355        // Check the accept and reject filters.
 356        //
 357        foreach (KeyValuePair<string, Regex> e in _accept)
 358        {
 359            if (!match(e.Key, e.Value, helper, false))
 360            {
 361                return null;
 362            }
 363        }
 364
 365        foreach (KeyValuePair<string, Regex> e in _reject)
 366        {
 367            if (match(e.Key, e.Value, helper, true))
 368            {
 369                return null;
 370            }
 371        }
 372
 373        //
 374        // Compute the key from the GroupBy property.
 375        //
 376        string key;
 377        try
 378        {
 379            if (_groupByAttributes.Count == 1)
 380            {
 381                key = helper.resolve(_groupByAttributes[0]);
 382            }
 383            else
 384            {
 385                var os = new StringBuilder();
 386                IEnumerator<string> q = _groupBySeparators.GetEnumerator();
 387                foreach (string p in _groupByAttributes)
 388                {
 389                    os.Append(helper.resolve(p));
 390                    if (q.MoveNext())
 391                    {
 392                        os.Append(q.Current);
 393                    }
 394                }
 395                key = os.ToString();
 396            }
 397        }
 398        catch (System.Exception)
 399        {
 400            return null;
 401        }
 402
 403        //
 404        // Lookup the metrics object.
 405        //
 406        lock (_mutex)
 407        {
 408            if (previous != null && previous.getId().Equals(key, StringComparison.Ordinal))
 409            {
 410                Debug.Assert(_objects[key] == previous);
 411                return previous;
 412            }
 413
 414            if (!_objects.TryGetValue(key, out Entry e))
 415            {
 416                try
 417                {
 418                    var t = new T();
 419                    t.id = key;
 420                    e = new Entry(this, t);
 421                    _objects.Add(key, e);
 422                }
 423                catch (System.Exception)
 424                {
 425                    Debug.Assert(false);
 426                    return null;
 427                }
 428            }
 429            e.attach(helper);
 430            return e;
 431        }
 432    }
 433
 434    private static Dictionary<string, Regex> parseRule(Ice.Properties properties, string name)
 435    {
 436        var pats = new Dictionary<string, Regex>();
 437        Dictionary<string, string> rules = properties.getPropertiesForPrefix(name + '.');
 438        foreach (KeyValuePair<string, string> e in rules)
 439        {
 440            pats.Add(e.Key[(name.Length + 1)..], new Regex(e.Value));
 441        }
 442        return pats;
 443    }
 444
 445    private static bool match(string attribute, Regex regex, IceMX.MetricsHelper<T> helper, bool reject)
 446    {
 447        string value;
 448        try
 449        {
 450            value = helper.resolve(attribute);
 451        }
 452        catch (System.Exception)
 453        {
 454            return !reject;
 455        }
 456        return regex.IsMatch(value);
 457    }
 458
 459    private void detached(Entry entry)
 460    {
 461        if (_retain == 0)
 462        {
 463            return;
 464        }
 465
 466        _detachedQueue ??= new LinkedList<Entry>();
 467        Debug.Assert(_detachedQueue.Count <= _retain);
 468
 469        // Compress the queue by removing entries which are no longer detached.
 470        LinkedListNode<Entry> p = _detachedQueue.First;
 471        while (p != null)
 472        {
 473            LinkedListNode<Entry> next = p.Next;
 474            if (p.Value == entry || !p.Value.isDetached())
 475            {
 476                _detachedQueue.Remove(p);
 477            }
 478            p = next;
 479        }
 480
 481        // If there's still no room, remove the oldest entry (at the front).
 482        if (_detachedQueue.Count == _retain)
 483        {
 484            _objects.Remove(_detachedQueue.First.Value.getId());
 485            _detachedQueue.RemoveFirst();
 486        }
 487
 488        // Add the entry at the back of the queue.
 489        _detachedQueue.AddLast(entry);
 490    }
 491
 492    internal readonly object _mutex = new();
 493    private readonly Dictionary<string, string> _properties;
 494    private readonly List<string> _groupByAttributes;
 495    private readonly List<string> _groupBySeparators;
 496    private readonly int _retain;
 497    private readonly Dictionary<string, Regex> _accept;
 498    private readonly Dictionary<string, Regex> _reject;
 499
 500    private readonly Dictionary<string, Entry> _objects = new();
 501    private readonly Dictionary<string, ISubMapCloneFactory> _subMaps;
 502    private LinkedList<Entry> _detachedQueue;
 503}
 504
 505internal class MetricsViewI
 506{
 507    internal MetricsViewI(string name) => _name = name;
 508
 509    internal bool addOrUpdateMap(
 510        Ice.Properties properties,
 511        string mapName,
 512        IMetricsMapFactory factory,
 513        Ice.Logger logger)
 514    {
 515        //
 516        // Add maps to views configured with the given map.
 517        //
 518        string viewPrefix = "IceMX.Metrics." + _name + ".";
 519        string mapsPrefix = viewPrefix + "Map.";
 520        Dictionary<string, string> mapsProps = properties.getPropertiesForPrefix(mapsPrefix);
 521
 522        string mapPrefix;
 523        _ = new Dictionary<string, string>();
 524        Dictionary<string, string> mapProps;
 525        if (mapsProps.Count > 0)
 526        {
 527            mapPrefix = mapsPrefix + mapName + ".";
 528            mapProps = properties.getPropertiesForPrefix(mapPrefix);
 529            if (mapProps.Count == 0)
 530            {
 531                // This map isn't configured for this view.
 532                return _maps.Remove(mapName);
 533            }
 534        }
 535        else
 536        {
 537            mapPrefix = viewPrefix;
 538            mapProps = properties.getPropertiesForPrefix(mapPrefix);
 539        }
 540
 541        if (properties.getPropertyAsInt(mapPrefix + "Disabled") > 0)
 542        {
 543            // This map is disabled for this view.
 544            return _maps.Remove(mapName);
 545        }
 546
 547        if (_maps.TryGetValue(mapName, out IMetricsMap m) && m.getProperties().DictionaryEqual(mapProps))
 548        {
 549            return false; // The map configuration didn't change, no need to re-create.
 550        }
 551
 552        try
 553        {
 554            _maps[mapName] = factory.create(mapPrefix, properties);
 555        }
 556        catch (System.Exception ex)
 557        {
 558            logger.warning("unexpected exception while creating metrics map:\n" + ex);
 559            _maps.Remove(mapName);
 560        }
 561        return true;
 562    }
 563
 564    internal bool removeMap(string mapName) => _maps.Remove(mapName);
 565
 566    internal Dictionary<string, IceMX.Metrics[]> getMetrics()
 567    {
 568        var metrics = new Dictionary<string, IceMX.Metrics[]>();
 569        foreach (KeyValuePair<string, IMetricsMap> e in _maps)
 570        {
 571            IceMX.Metrics[] m = e.Value.getMetrics();
 572            if (m != null)
 573            {
 574                metrics.Add(e.Key, m);
 575            }
 576        }
 577        return metrics;
 578    }
 579
 580    internal IceMX.MetricsFailures[] getFailures(string mapName)
 581    {
 582        if (_maps.TryGetValue(mapName, out IMetricsMap m))
 583        {
 584            return m.getFailures();
 585        }
 586        return [];
 587    }
 588
 589    internal IceMX.MetricsFailures getFailures(string mapName, string id)
 590    {
 591        if (_maps.TryGetValue(mapName, out IMetricsMap m))
 592        {
 593            return m.getFailures(id);
 594        }
 595        return new(failures: []);
 596    }
 597
 598    internal ICollection<string> getMaps() => _maps.Keys;
 599
 600    internal MetricsMap<T> getMap<T>(string mapName) where T : IceMX.Metrics, new()
 601    {
 602        if (_maps.TryGetValue(mapName, out IMetricsMap m))
 603        {
 604            return (MetricsMap<T>)m;
 605        }
 606        return null;
 607    }
 608
 609    private readonly string _name;
 610    private readonly Dictionary<string, IMetricsMap> _maps = new Dictionary<string, IMetricsMap>();
 611}
 612
 613public class MetricsAdminI : IceMX.MetricsAdminDisp_
 614{
 615    private static readonly string[] suffixes =
 616        {
 617            "Disabled",
 618            "GroupBy",
 619            "Accept.*",
 620            "Reject.*",
 621            "RetainDetached",
 622            "Map.*",
 623        };
 624
 625    public static void validateProperties(string prefix, Ice.Properties properties)
 626    {
 627        Dictionary<string, string> props = properties.getPropertiesForPrefix(prefix);
 628        var unknownProps = new List<string>();
 629        foreach (string prop in props.Keys)
 630        {
 631            bool valid = false;
 632            foreach (string suffix in suffixes)
 633            {
 634                if (Ice.UtilInternal.StringUtil.match(prop, prefix + suffix, false))
 635                {
 636                    valid = true;
 637                    break;
 638                }
 639            }
 640
 641            if (!valid)
 642            {
 643                unknownProps.Add(prop);
 644            }
 645        }
 646
 647        if (unknownProps.Count != 0)
 648        {
 649            var message = new StringBuilder("Found unknown IceMX properties for '");
 650            message.Append(prefix.AsSpan(0, prefix.Length - 1));
 651            message.Append("':");
 652            foreach (string p in unknownProps)
 653            {
 654                message.Append("\n    ");
 655                message.Append(p);
 656            }
 657            throw new PropertyException(message.ToString());
 658        }
 659    }
 660
 661    private class MetricsMapFactory<T> : IMetricsMapFactory where T : IceMX.Metrics, new()
 662    {
 663        public MetricsMapFactory(Action updater) => _updater = updater;
 664
 665        public void update()
 666        {
 667            Debug.Assert(_updater != null);
 668            _updater();
 669        }
 670
 671        public IMetricsMap create(string mapPrefix, Ice.Properties properties) =>
 672            new MetricsMap<T>(mapPrefix, properties, _subMaps);
 673
 674        public void registerSubMap<S>(string subMap, System.Reflection.FieldInfo field)
 675            where S : IceMX.Metrics, new() => _subMaps.Add(subMap, new SubMapFactory<S>(field));
 676
 677        private readonly Action _updater;
 678        private readonly Dictionary<string, ISubMapFactory> _subMaps = new Dictionary<string, ISubMapFactory>();
 679    }
 680
 681    public MetricsAdminI(Ice.Properties properties, Ice.Logger logger)
 682    {
 683        _logger = logger;
 684        _properties = properties;
 685        updateViews();
 686    }
 687
 688    public void updateViews()
 689    {
 690        var updatedMaps = new HashSet<IMetricsMapFactory>();
 691        lock (_mutex)
 692        {
 693            string viewsPrefix = "IceMX.Metrics.";
 694            Dictionary<string, string> viewsProps = _properties.getPropertiesForPrefix(viewsPrefix);
 695            var views = new Dictionary<string, MetricsViewI>();
 696            _disabledViews.Clear();
 697            foreach (KeyValuePair<string, string> e in viewsProps)
 698            {
 699                string viewName = e.Key[viewsPrefix.Length..];
 700                int dotPos = viewName.IndexOf('.', StringComparison.Ordinal);
 701                if (dotPos > 0)
 702                {
 703                    viewName = viewName[..dotPos];
 704                }
 705
 706                if (views.ContainsKey(viewName) || _disabledViews.Contains(viewName))
 707                {
 708                    continue; // View already configured.
 709                }
 710
 711                validateProperties(viewsPrefix + viewName + ".", _properties);
 712
 713                if (_properties.getPropertyAsIntWithDefault(viewsPrefix + viewName + ".Disabled", 0) > 0)
 714                {
 715                    _disabledViews.Add(viewName);
 716                    continue; // The view is disabled
 717                }
 718
 719                //
 720                // Create the view or update it.
 721                //
 722                if (!_views.TryGetValue(viewName, out MetricsViewI v))
 723                {
 724                    v = new MetricsViewI(viewName);
 725                }
 726                views[viewName] = v;
 727
 728                foreach (KeyValuePair<string, IMetricsMapFactory> f in _factories)
 729                {
 730                    if (v.addOrUpdateMap(_properties, f.Key, f.Value, _logger))
 731                    {
 732                        updatedMaps.Add(f.Value);
 733                    }
 734                }
 735            }
 736
 737            Dictionary<string, MetricsViewI> tmp = _views;
 738            _views = views;
 739            views = tmp;
 740
 741            //
 742            // Go through removed views to collect maps to update.
 743            //
 744            foreach (KeyValuePair<string, MetricsViewI> v in views)
 745            {
 746                if (!_views.ContainsKey(v.Key))
 747                {
 748                    foreach (string n in v.Value.getMaps())
 749                    {
 750                        updatedMaps.Add(_factories[n]);
 751                    }
 752                }
 753            }
 754        }
 755
 756        //
 757        // Call the updaters to update the maps.
 758        //
 759        foreach (IMetricsMapFactory f in updatedMaps)
 760        {
 761            f.update();
 762        }
 763    }
 764
 765    public override string[] getMetricsViewNames(out string[] disabledViews, Ice.Current current)
 766    {
 767        lock (_mutex)
 768        {
 769            disabledViews = _disabledViews.ToArray();
 770            return new List<string>(_views.Keys).ToArray();
 771        }
 772    }
 773
 774    public override void enableMetricsView(string name, Ice.Current current)
 775    {
 776        lock (_mutex)
 777        {
 778            getMetricsView(name); // Throws if unknown view.
 779            _properties.setProperty("IceMX.Metrics." + name + ".Disabled", "0");
 780        }
 781        updateViews();
 782    }
 783
 784    public override void disableMetricsView(string name, Ice.Current current)
 785    {
 786        lock (_mutex)
 787        {
 788            getMetricsView(name); // Throws if unknown view.
 789            _properties.setProperty("IceMX.Metrics." + name + ".Disabled", "1");
 790        }
 791        updateViews();
 792    }
 793
 794    public override Dictionary<string, IceMX.Metrics[]>
 795    getMetricsView(string view, out long timestamp, Ice.Current current)
 796    {
 797        lock (_mutex)
 798        {
 799            MetricsViewI viewI = getMetricsView(view);
 800            timestamp = Time.currentMonotonicTimeMillis();
 801            if (viewI is not null)
 802            {
 803                return viewI.getMetrics();
 804            }
 805            return new Dictionary<string, IceMX.Metrics[]>();
 806        }
 807    }
 808
 809    public override IceMX.MetricsFailures[] getMapMetricsFailures(string view, string map, Ice.Current current)
 810    {
 811        lock (_mutex)
 812        {
 813            MetricsViewI viewI = getMetricsView(view);
 814            if (viewI is not null)
 815            {
 816                return viewI.getFailures(map);
 817            }
 818            return [];
 819        }
 820    }
 821
 822    public override IceMX.MetricsFailures getMetricsFailures(
 823        string view,
 824        string map,
 825        string id,
 826        Ice.Current current)
 827    {
 828        lock (_mutex)
 829        {
 830            MetricsViewI viewI = getMetricsView(view);
 831            if (viewI is not null)
 832            {
 833                return viewI.getFailures(map, id);
 834            }
 835            return new(failures: []);
 836        }
 837    }
 838
 839    public void registerMap<T>(string map, Action updater)
 840        where T : IceMX.Metrics, new()
 841    {
 842        bool updated;
 843        MetricsMapFactory<T> factory;
 844        lock (_mutex)
 845        {
 846            factory = new MetricsMapFactory<T>(updater);
 847            _factories.Add(map, factory);
 848            updated = addOrUpdateMap(map, factory);
 849        }
 850        if (updated)
 851        {
 852            factory.update();
 853        }
 854    }
 855
 856    public void registerSubMap<S>(string map, string subMap, System.Reflection.FieldInfo field)
 857        where S : IceMX.Metrics, new()
 858    {
 859        bool updated;
 860        IMetricsMapFactory factory;
 861        lock (_mutex)
 862        {
 863            if (!_factories.TryGetValue(map, out factory))
 864            {
 865                return;
 866            }
 867            factory.registerSubMap<S>(subMap, field);
 868            removeMap(map);
 869            updated = addOrUpdateMap(map, factory);
 870        }
 871        if (updated)
 872        {
 873            factory.update();
 874        }
 875    }
 876
 877    public void unregisterMap(string mapName)
 878    {
 879        bool updated;
 880        IMetricsMapFactory factory;
 881        lock (_mutex)
 882        {
 883            if (!_factories.TryGetValue(mapName, out factory))
 884            {
 885                return;
 886            }
 887            _factories.Remove(mapName);
 888            updated = removeMap(mapName);
 889        }
 890        if (updated)
 891        {
 892            factory.update();
 893        }
 894    }
 895
 896    public List<MetricsMap<T>> getMaps<T>(string mapName) where T : IceMX.Metrics, new()
 897    {
 898        var maps = new List<MetricsMap<T>>();
 899        lock (_mutex)
 900        {
 901            foreach (MetricsViewI v in _views.Values)
 902            {
 903                MetricsMap<T> map = v.getMap<T>(mapName);
 904                if (map is not null)
 905                {
 906                    maps.Add(map);
 907                }
 908            }
 909        }
 910        return maps;
 911    }
 912
 913    public Ice.Logger getLogger() => _logger;
 914
 915    internal void updated(Dictionary<string, string> changes)
 916    {
 917        foreach (KeyValuePair<string, string> e in changes)
 918        {
 919            if (e.Key.StartsWith("IceMX.", StringComparison.Ordinal))
 920            {
 921                // Update the metrics views using the new configuration.
 922                try
 923                {
 924                    updateViews();
 925                }
 926                catch (System.Exception ex)
 927                {
 928                    _logger.warning("unexpected exception while updating metrics view configuration:\n" +
 929                                    ex.ToString());
 930                }
 931                return;
 932            }
 933        }
 934    }
 935
 936    private MetricsViewI getMetricsView(string name)
 937    {
 938        if (!_views.TryGetValue(name, out MetricsViewI view))
 939        {
 940            if (!_disabledViews.Contains(name))
 941            {
 942                throw new IceMX.UnknownMetricsView();
 943            }
 944            return null;
 945        }
 946        return view;
 947    }
 948
 949    private bool addOrUpdateMap(string mapName, IMetricsMapFactory factory)
 950    {
 951        bool updated = false;
 952        foreach (MetricsViewI v in _views.Values)
 953        {
 954            updated |= v.addOrUpdateMap(_properties, mapName, factory, _logger);
 955        }
 956        return updated;
 957    }
 958
 959    private bool removeMap(string mapName)
 960    {
 961        bool updated = false;
 962        foreach (MetricsViewI v in _views.Values)
 963        {
 964            updated |= v.removeMap(mapName);
 965        }
 966        return updated;
 967    }
 968
 969    private readonly Ice.Properties _properties;
 970
 971    private readonly Ice.Logger _logger;
 972
 973    private readonly Dictionary<string, IMetricsMapFactory> _factories =
 974        new Dictionary<string, IMetricsMapFactory>();
 975
 976    private Dictionary<string, MetricsViewI> _views = new Dictionary<string, MetricsViewI>();
 977
 978    private readonly List<string> _disabledViews = new List<string>();
 979    private readonly object _mutex = new();
 980}