< Summary

Information
Class: Ice.Internal.MetricsViewI
Assembly: Ice
File(s): /home/runner/work/ice/ice/csharp/src/Ice/Internal/MetricsAdminI.cs
Tag: 71_18251537082
Line coverage
78%
Covered lines: 32
Uncovered lines: 9
Coverable lines: 41
Total lines: 978
Line coverage: 78%
Branch coverage
72%
Covered branches: 16
Total branches: 22
Branch coverage: 72.7%
Method coverage
87%
Covered methods: 7
Total methods: 8
Method coverage: 87.5%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
addOrUpdateMap(...)75%13.691277.27%
removeMap(...)100%11100%
getMetrics()100%44100%
getFailures(...)0%620%
getFailures(...)50%2.15266.67%
getMaps()100%11100%
getMap<T>(...)100%22100%

File(s)

/home/runner/work/ice/ice/csharp/src/Ice/Internal/MetricsAdminI.cs

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3using System;
 4using System.Collections.Generic;
 5using System.Diagnostics;
 6using System.Text;
 7using System.Text.RegularExpressions;
 8
 9namespace Ice.Internal;
 10
 11internal interface IMetricsMap
 12{
 13    IceMX.Metrics[] getMetrics();
 14
 15    IceMX.MetricsFailures[] getFailures();
 16
 17    IceMX.MetricsFailures getFailures(string id);
 18
 19    Dictionary<string, string> getProperties();
 20}
 21
 22internal interface ISubMap
 23{
 24    void addSubMapToMetrics(IceMX.Metrics metrics);
 25}
 26
 27internal interface ISubMapCloneFactory
 28{
 29    ISubMap create();
 30}
 31
 32internal interface ISubMapFactory
 33{
 34    ISubMapCloneFactory createCloneFactory(string subMapPrefix, Ice.Properties properties);
 35}
 36
 37internal interface IMetricsMapFactory
 38{
 39    void registerSubMap<S>(string subMap, System.Reflection.FieldInfo field) where S : IceMX.Metrics, new();
 40
 41    void update();
 42
 43    IMetricsMap create(string mapPrefix, Ice.Properties properties);
 44}
 45
 46internal class SubMap<S> : ISubMap where S : IceMX.Metrics, new()
 47{
 48    internal SubMap(MetricsMap<S> map, System.Reflection.FieldInfo field)
 49    {
 50        _map = map;
 51        _field = field;
 52    }
 53
 54    internal MetricsMap<S>.Entry getMatching(IceMX.MetricsHelper<S> helper) => _map.getMatching(helper, null);
 55
 56    public void addSubMapToMetrics(IceMX.Metrics metrics)
 57    {
 58        try
 59        {
 60            _field.SetValue(metrics, _map.getMetrics());
 61        }
 62        catch (System.Exception)
 63        {
 64            Debug.Assert(false);
 65        }
 66    }
 67
 68    private readonly MetricsMap<S> _map;
 69    private readonly System.Reflection.FieldInfo _field;
 70}
 71
 72internal class SubMapCloneFactory<S> : ISubMapCloneFactory where S : IceMX.Metrics, new()
 73{
 74    internal SubMapCloneFactory(MetricsMap<S> map, System.Reflection.FieldInfo field)
 75    {
 76        _map = map;
 77        _field = field;
 78    }
 79
 80    public ISubMap create() => new SubMap<S>(new MetricsMap<S>(_map), _field);
 81
 82    private readonly MetricsMap<S> _map;
 83    private readonly System.Reflection.FieldInfo _field;
 84}
 85
 86internal class SubMapFactory<S> : ISubMapFactory where S : IceMX.Metrics, new()
 87{
 88    internal SubMapFactory(System.Reflection.FieldInfo field) => _field = field;
 89
 90    public ISubMapCloneFactory createCloneFactory(
 91        string subMapPrefix,
 92        Ice.Properties properties) =>
 93        new SubMapCloneFactory<S>(new MetricsMap<S>(subMapPrefix, properties, null), _field);
 94
 95    private readonly System.Reflection.FieldInfo _field;
 96}
 97
 98public class MetricsMap<T> : IMetricsMap where T : IceMX.Metrics, new()
 99{
 100    public class Entry
 101    {
 102        internal Entry(MetricsMap<T> map, T obj)
 103        {
 104            _map = map;
 105            _object = obj;
 106        }
 107
 108        public void failed(string exceptionName)
 109        {
 110            lock (_map._mutex)
 111            {
 112                ++_object.failures;
 113                _failures ??= new Dictionary<string, int>();
 114                if (_failures.TryGetValue(exceptionName, out int count))
 115                {
 116                    _failures[exceptionName] = count + 1;
 117                }
 118                else
 119                {
 120                    _failures[exceptionName] = 1;
 121                }
 122            }
 123        }
 124
 125        internal MetricsMap<S>.Entry getMatching<S>(string mapName, IceMX.MetricsHelper<S> helper)
 126            where S : IceMX.Metrics, new()
 127        {
 128            ISubMap m;
 129            lock (_map._mutex)
 130            {
 131                if (_subMaps == null || !_subMaps.TryGetValue(mapName, out m))
 132                {
 133                    m = _map.createSubMap(mapName);
 134                    if (m == null)
 135                    {
 136                        return null;
 137                    }
 138                    _subMaps ??= new Dictionary<string, ISubMap>();
 139                    _subMaps.Add(mapName, m);
 140                }
 141            }
 142            return ((SubMap<S>)m).getMatching(helper);
 143        }
 144
 145        public void detach(long lifetime)
 146        {
 147            lock (_map._mutex)
 148            {
 149                _object.totalLifetime += lifetime;
 150                if (--_object.current == 0)
 151                {
 152                    _map.detached(this);
 153                }
 154            }
 155        }
 156
 157        public void execute(IceMX.Observer<T>.MetricsUpdate func)
 158        {
 159            lock (_map)
 160            {
 161                func(_object);
 162            }
 163        }
 164
 165        public MetricsMap<T> getMap() => _map;
 166
 167        internal IceMX.MetricsFailures getFailures()
 168        {
 169            if (_failures == null)
 170            {
 171                return new(failures: []);
 172            }
 173            return new(_object.id, new Dictionary<string, int>(_failures));
 174        }
 175
 176        internal void attach(IceMX.MetricsHelper<T> helper)
 177        {
 178            ++_object.total;
 179            ++_object.current;
 180            helper.initMetrics(_object);
 181        }
 182
 183        internal bool isDetached() => _object.current == 0;
 184
 185        internal IceMX.Metrics clone()
 186        {
 187            var metrics = (T)_object.Clone();
 188            if (_subMaps != null)
 189            {
 190                foreach (ISubMap s in _subMaps.Values)
 191                {
 192                    s.addSubMapToMetrics(metrics);
 193                }
 194            }
 195            return metrics;
 196        }
 197
 198        internal string getId() => _object.id;
 199
 200        private readonly MetricsMap<T> _map;
 201        private readonly T _object;
 202        private Dictionary<string, int> _failures;
 203        private Dictionary<string, ISubMap> _subMaps;
 204    }
 205
 206    internal MetricsMap(string mapPrefix, Ice.Properties props, Dictionary<string, ISubMapFactory> subMaps)
 207    {
 208        MetricsAdminI.validateProperties(mapPrefix, props);
 209        _properties = props.getPropertiesForPrefix(mapPrefix);
 210
 211        _retain = props.getPropertyAsIntWithDefault(mapPrefix + "RetainDetached", 10);
 212        _accept = parseRule(props, mapPrefix + "Accept");
 213        _reject = parseRule(props, mapPrefix + "Reject");
 214        _groupByAttributes = new List<string>();
 215        _groupBySeparators = new List<string>();
 216
 217        string groupBy = props.getPropertyWithDefault(mapPrefix + "GroupBy", "id");
 218        if (groupBy.Length > 0)
 219        {
 220            string v = "";
 221            bool attribute = char.IsLetter(groupBy[0]) || char.IsDigit(groupBy[0]);
 222            if (!attribute)
 223            {
 224                _groupByAttributes.Add("");
 225            }
 226
 227            foreach (char p in groupBy)
 228            {
 229                bool isAlphaNum = char.IsLetter(p) || char.IsDigit(p) || p == '.';
 230                if (attribute && !isAlphaNum)
 231                {
 232                    _groupByAttributes.Add(v);
 233                    v = "" + p;
 234                    attribute = false;
 235                }
 236                else if (!attribute && isAlphaNum)
 237                {
 238                    _groupBySeparators.Add(v);
 239                    v = "" + p;
 240                    attribute = true;
 241                }
 242                else
 243                {
 244                    v += p;
 245                }
 246            }
 247
 248            if (attribute)
 249            {
 250                _groupByAttributes.Add(v);
 251            }
 252            else
 253            {
 254                _groupBySeparators.Add(v);
 255            }
 256        }
 257
 258        if (subMaps != null && subMaps.Count > 0)
 259        {
 260            _subMaps = new Dictionary<string, ISubMapCloneFactory>();
 261
 262            var subMapNames = new List<string>();
 263            foreach (KeyValuePair<string, ISubMapFactory> e in subMaps)
 264            {
 265                subMapNames.Add(e.Key);
 266                string subMapsPrefix = mapPrefix + "Map.";
 267                string subMapPrefix = subMapsPrefix + e.Key + '.';
 268                if (props.getPropertiesForPrefix(subMapPrefix).Count == 0)
 269                {
 270                    if (props.getPropertiesForPrefix(subMapsPrefix).Count == 0)
 271                    {
 272                        subMapPrefix = mapPrefix;
 273                    }
 274                    else
 275                    {
 276                        continue; // This sub-map isn't configured.
 277                    }
 278                }
 279
 280                _subMaps.Add(e.Key, e.Value.createCloneFactory(subMapPrefix, props));
 281            }
 282        }
 283        else
 284        {
 285            _subMaps = null;
 286        }
 287    }
 288
 289    internal MetricsMap(MetricsMap<T> map)
 290    {
 291        _properties = map._properties;
 292        _groupByAttributes = map._groupByAttributes;
 293        _groupBySeparators = map._groupBySeparators;
 294        _retain = map._retain;
 295        _accept = map._accept;
 296        _reject = map._reject;
 297        _subMaps = map._subMaps;
 298    }
 299
 300    public Dictionary<string, string> getProperties() => _properties;
 301
 302    public IceMX.Metrics[] getMetrics()
 303    {
 304        lock (_mutex)
 305        {
 306            var metrics = new IceMX.Metrics[_objects.Count];
 307            int i = 0;
 308            foreach (Entry e in _objects.Values)
 309            {
 310                metrics[i++] = e.clone();
 311            }
 312            return metrics;
 313        }
 314    }
 315
 316    public IceMX.MetricsFailures[] getFailures()
 317    {
 318        lock (_mutex)
 319        {
 320            var failures = new List<IceMX.MetricsFailures>();
 321            foreach (Entry e in _objects.Values)
 322            {
 323                IceMX.MetricsFailures f = e.getFailures();
 324                if (f != null)
 325                {
 326                    failures.Add(f);
 327                }
 328            }
 329            return failures.ToArray();
 330        }
 331    }
 332
 333    public IceMX.MetricsFailures getFailures(string id)
 334    {
 335        lock (_mutex)
 336        {
 337            return _objects.TryGetValue(id, out Entry e) ? e.getFailures() : new(failures: []);
 338        }
 339    }
 340
 341    private ISubMap createSubMap(string subMapName)
 342    {
 343        if (_subMaps == null)
 344        {
 345            return null;
 346        }
 347        if (_subMaps.TryGetValue(subMapName, out ISubMapCloneFactory factory))
 348        {
 349            return factory.create();
 350        }
 351        return null;
 352    }
 353
 354    public Entry getMatching(IceMX.MetricsHelper<T> helper, Entry previous)
 355    {
 356        //
 357        // Check the accept and reject filters.
 358        //
 359        foreach (KeyValuePair<string, Regex> e in _accept)
 360        {
 361            if (!match(e.Key, e.Value, helper, false))
 362            {
 363                return null;
 364            }
 365        }
 366
 367        foreach (KeyValuePair<string, Regex> e in _reject)
 368        {
 369            if (match(e.Key, e.Value, helper, true))
 370            {
 371                return null;
 372            }
 373        }
 374
 375        //
 376        // Compute the key from the GroupBy property.
 377        //
 378        string key;
 379        try
 380        {
 381            if (_groupByAttributes.Count == 1)
 382            {
 383                key = helper.resolve(_groupByAttributes[0]);
 384            }
 385            else
 386            {
 387                var os = new StringBuilder();
 388                IEnumerator<string> q = _groupBySeparators.GetEnumerator();
 389                foreach (string p in _groupByAttributes)
 390                {
 391                    os.Append(helper.resolve(p));
 392                    if (q.MoveNext())
 393                    {
 394                        os.Append(q.Current);
 395                    }
 396                }
 397                key = os.ToString();
 398            }
 399        }
 400        catch (System.Exception)
 401        {
 402            return null;
 403        }
 404
 405        //
 406        // Lookup the metrics object.
 407        //
 408        lock (_mutex)
 409        {
 410            if (previous != null && previous.getId().Equals(key, StringComparison.Ordinal))
 411            {
 412                Debug.Assert(_objects[key] == previous);
 413                return previous;
 414            }
 415
 416            if (!_objects.TryGetValue(key, out Entry e))
 417            {
 418                try
 419                {
 420                    var t = new T();
 421                    t.id = key;
 422                    e = new Entry(this, t);
 423                    _objects.Add(key, e);
 424                }
 425                catch (System.Exception)
 426                {
 427                    Debug.Assert(false);
 428                }
 429            }
 430            e.attach(helper);
 431            return e;
 432        }
 433    }
 434
 435    private static Dictionary<string, Regex> parseRule(Ice.Properties properties, string name)
 436    {
 437        var pats = new Dictionary<string, Regex>();
 438        Dictionary<string, string> rules = properties.getPropertiesForPrefix(name + '.');
 439        foreach (KeyValuePair<string, string> e in rules)
 440        {
 441            pats.Add(e.Key[(name.Length + 1)..], new Regex(e.Value));
 442        }
 443        return pats;
 444    }
 445
 446    private static bool match(string attribute, Regex regex, IceMX.MetricsHelper<T> helper, bool reject)
 447    {
 448        string value;
 449        try
 450        {
 451            value = helper.resolve(attribute);
 452        }
 453        catch (System.Exception)
 454        {
 455            return !reject;
 456        }
 457        return regex.IsMatch(value);
 458    }
 459
 460    private void detached(Entry entry)
 461    {
 462        if (_retain == 0)
 463        {
 464            return;
 465        }
 466
 467        _detachedQueue ??= new LinkedList<Entry>();
 468        Debug.Assert(_detachedQueue.Count <= _retain);
 469
 470        // Compress the queue by removing entries which are no longer detached.
 471        LinkedListNode<Entry> p = _detachedQueue.First;
 472        while (p != null)
 473        {
 474            LinkedListNode<Entry> next = p.Next;
 475            if (p.Value == entry || !p.Value.isDetached())
 476            {
 477                _detachedQueue.Remove(p);
 478            }
 479            p = next;
 480        }
 481
 482        // If there's still no room, remove the oldest entry (at the front).
 483        if (_detachedQueue.Count == _retain)
 484        {
 485            _objects.Remove(_detachedQueue.First.Value.getId());
 486            _detachedQueue.RemoveFirst();
 487        }
 488
 489        // Add the entry at the back of the queue.
 490        _detachedQueue.AddLast(entry);
 491    }
 492
 493    internal readonly object _mutex = new();
 494    private readonly Dictionary<string, string> _properties;
 495    private readonly List<string> _groupByAttributes;
 496    private readonly List<string> _groupBySeparators;
 497    private readonly int _retain;
 498    private readonly Dictionary<string, Regex> _accept;
 499    private readonly Dictionary<string, Regex> _reject;
 500
 501    private readonly Dictionary<string, Entry> _objects = new();
 502    private readonly Dictionary<string, ISubMapCloneFactory> _subMaps;
 503    private LinkedList<Entry> _detachedQueue;
 504}
 505
 506internal class MetricsViewI
 507{
 1508    internal MetricsViewI(string name) => _name = name;
 509
 510    internal bool addOrUpdateMap(
 511        Ice.Properties properties,
 512        string mapName,
 513        IMetricsMapFactory factory,
 514        Ice.Logger logger)
 515    {
 516        //
 517        // Add maps to views configured with the given map.
 518        //
 1519        string viewPrefix = "IceMX.Metrics." + _name + ".";
 1520        string mapsPrefix = viewPrefix + "Map.";
 1521        Dictionary<string, string> mapsProps = properties.getPropertiesForPrefix(mapsPrefix);
 522
 523        string mapPrefix;
 1524        _ = new Dictionary<string, string>();
 525        Dictionary<string, string> mapProps;
 1526        if (mapsProps.Count > 0)
 527        {
 1528            mapPrefix = mapsPrefix + mapName + ".";
 1529            mapProps = properties.getPropertiesForPrefix(mapPrefix);
 1530            if (mapProps.Count == 0)
 531            {
 532                // This map isn't configured for this view.
 1533                return _maps.Remove(mapName);
 534            }
 535        }
 536        else
 537        {
 1538            mapPrefix = viewPrefix;
 1539            mapProps = properties.getPropertiesForPrefix(mapPrefix);
 540        }
 541
 1542        if (properties.getPropertyAsInt(mapPrefix + "Disabled") > 0)
 543        {
 544            // This map is disabled for this view.
 0545            return _maps.Remove(mapName);
 546        }
 547
 1548        if (_maps.TryGetValue(mapName, out IMetricsMap m) && m.getProperties().DictionaryEqual(mapProps))
 549        {
 1550            return false; // The map configuration didn't change, no need to re-create.
 551        }
 552
 553        try
 554        {
 1555            _maps[mapName] = factory.create(mapPrefix, properties);
 1556        }
 0557        catch (System.Exception ex)
 558        {
 0559            logger.warning("unexpected exception while creating metrics map:\n" + ex);
 0560            _maps.Remove(mapName);
 0561        }
 1562        return true;
 563    }
 564
 1565    internal bool removeMap(string mapName) => _maps.Remove(mapName);
 566
 567    internal Dictionary<string, IceMX.Metrics[]> getMetrics()
 568    {
 1569        var metrics = new Dictionary<string, IceMX.Metrics[]>();
 1570        foreach (KeyValuePair<string, IMetricsMap> e in _maps)
 571        {
 1572            IceMX.Metrics[] m = e.Value.getMetrics();
 1573            if (m != null)
 574            {
 1575                metrics.Add(e.Key, m);
 576            }
 577        }
 1578        return metrics;
 579    }
 580
 581    internal IceMX.MetricsFailures[] getFailures(string mapName)
 582    {
 0583        if (_maps.TryGetValue(mapName, out IMetricsMap m))
 584        {
 0585            return m.getFailures();
 586        }
 0587        return [];
 588    }
 589
 590    internal IceMX.MetricsFailures getFailures(string mapName, string id)
 591    {
 1592        if (_maps.TryGetValue(mapName, out IMetricsMap m))
 593        {
 1594            return m.getFailures(id);
 595        }
 0596        return new(failures: []);
 597    }
 598
 1599    internal ICollection<string> getMaps() => _maps.Keys;
 600
 601    internal MetricsMap<T> getMap<T>(string mapName) where T : IceMX.Metrics, new()
 602    {
 1603        if (_maps.TryGetValue(mapName, out IMetricsMap m))
 604        {
 1605            return (MetricsMap<T>)m;
 606        }
 1607        return null;
 608    }
 609
 610    private readonly string _name;
 1611    private readonly Dictionary<string, IMetricsMap> _maps = new Dictionary<string, IMetricsMap>();
 612}
 613
 614public class MetricsAdminI : IceMX.MetricsAdminDisp_
 615{
 616    private static readonly string[] suffixes =
 617        {
 618            "Disabled",
 619            "GroupBy",
 620            "Accept.*",
 621            "Reject.*",
 622            "RetainDetached",
 623            "Map.*",
 624        };
 625
 626    public static void validateProperties(string prefix, Ice.Properties properties)
 627    {
 628        Dictionary<string, string> props = properties.getPropertiesForPrefix(prefix);
 629        var unknownProps = new List<string>();
 630        foreach (string prop in props.Keys)
 631        {
 632            bool valid = false;
 633            foreach (string suffix in suffixes)
 634            {
 635                if (Ice.UtilInternal.StringUtil.match(prop, prefix + suffix, false))
 636                {
 637                    valid = true;
 638                    break;
 639                }
 640            }
 641
 642            if (!valid)
 643            {
 644                unknownProps.Add(prop);
 645            }
 646        }
 647
 648        if (unknownProps.Count != 0)
 649        {
 650            var message = new StringBuilder("Found unknown IceMX properties for '");
 651            message.Append(prefix.AsSpan(0, prefix.Length - 1));
 652            message.Append("':");
 653            foreach (string p in unknownProps)
 654            {
 655                message.Append("\n    ");
 656                message.Append(p);
 657            }
 658            throw new PropertyException(message.ToString());
 659        }
 660    }
 661
 662    private class MetricsMapFactory<T> : IMetricsMapFactory where T : IceMX.Metrics, new()
 663    {
 664        public MetricsMapFactory(Action updater) => _updater = updater;
 665
 666        public void update()
 667        {
 668            Debug.Assert(_updater != null);
 669            _updater();
 670        }
 671
 672        public IMetricsMap create(string mapPrefix, Ice.Properties properties) =>
 673            new MetricsMap<T>(mapPrefix, properties, _subMaps);
 674
 675        public void registerSubMap<S>(string subMap, System.Reflection.FieldInfo field)
 676            where S : IceMX.Metrics, new() => _subMaps.Add(subMap, new SubMapFactory<S>(field));
 677
 678        private readonly Action _updater;
 679        private readonly Dictionary<string, ISubMapFactory> _subMaps = new Dictionary<string, ISubMapFactory>();
 680    }
 681
 682    public MetricsAdminI(Ice.Properties properties, Ice.Logger logger)
 683    {
 684        _logger = logger;
 685        _properties = properties;
 686        updateViews();
 687    }
 688
 689    public void updateViews()
 690    {
 691        var updatedMaps = new HashSet<IMetricsMapFactory>();
 692        lock (_mutex)
 693        {
 694            string viewsPrefix = "IceMX.Metrics.";
 695            Dictionary<string, string> viewsProps = _properties.getPropertiesForPrefix(viewsPrefix);
 696            var views = new Dictionary<string, MetricsViewI>();
 697            _disabledViews.Clear();
 698            foreach (KeyValuePair<string, string> e in viewsProps)
 699            {
 700                string viewName = e.Key[viewsPrefix.Length..];
 701                int dotPos = viewName.IndexOf('.', StringComparison.Ordinal);
 702                if (dotPos > 0)
 703                {
 704                    viewName = viewName[..dotPos];
 705                }
 706
 707                if (views.ContainsKey(viewName) || _disabledViews.Contains(viewName))
 708                {
 709                    continue; // View already configured.
 710                }
 711
 712                validateProperties(viewsPrefix + viewName + ".", _properties);
 713
 714                if (_properties.getPropertyAsIntWithDefault(viewsPrefix + viewName + ".Disabled", 0) > 0)
 715                {
 716                    _disabledViews.Add(viewName);
 717                    continue; // The view is disabled
 718                }
 719
 720                //
 721                // Create the view or update it.
 722                //
 723                if (!_views.TryGetValue(viewName, out MetricsViewI v))
 724                {
 725                    v = new MetricsViewI(viewName);
 726                }
 727                views[viewName] = v;
 728
 729                foreach (KeyValuePair<string, IMetricsMapFactory> f in _factories)
 730                {
 731                    if (v.addOrUpdateMap(_properties, f.Key, f.Value, _logger))
 732                    {
 733                        updatedMaps.Add(f.Value);
 734                    }
 735                }
 736            }
 737
 738            Dictionary<string, MetricsViewI> tmp = _views;
 739            _views = views;
 740            views = tmp;
 741
 742            //
 743            // Go through removed views to collect maps to update.
 744            //
 745            foreach (KeyValuePair<string, MetricsViewI> v in views)
 746            {
 747                if (!_views.ContainsKey(v.Key))
 748                {
 749                    foreach (string n in v.Value.getMaps())
 750                    {
 751                        updatedMaps.Add(_factories[n]);
 752                    }
 753                }
 754            }
 755        }
 756
 757        //
 758        // Call the updaters to update the maps.
 759        //
 760        foreach (IMetricsMapFactory f in updatedMaps)
 761        {
 762            f.update();
 763        }
 764    }
 765
 766    public override string[] getMetricsViewNames(out string[] disabledViews, Ice.Current current)
 767    {
 768        lock (_mutex)
 769        {
 770            disabledViews = _disabledViews.ToArray();
 771            return new List<string>(_views.Keys).ToArray();
 772        }
 773    }
 774
 775    public override void enableMetricsView(string name, Ice.Current current)
 776    {
 777        lock (_mutex)
 778        {
 779            getMetricsView(name); // Throws if unknown view.
 780            _properties.setProperty("IceMX.Metrics." + name + ".Disabled", "0");
 781        }
 782        updateViews();
 783    }
 784
 785    public override void disableMetricsView(string name, Ice.Current current)
 786    {
 787        lock (_mutex)
 788        {
 789            getMetricsView(name); // Throws if unknown view.
 790            _properties.setProperty("IceMX.Metrics." + name + ".Disabled", "1");
 791        }
 792        updateViews();
 793    }
 794
 795    public override Dictionary<string, IceMX.Metrics[]>
 796    getMetricsView(string view, out long timestamp, Ice.Current current)
 797    {
 798        lock (_mutex)
 799        {
 800            MetricsViewI viewI = getMetricsView(view);
 801            timestamp = Time.currentMonotonicTimeMillis();
 802            if (viewI is not null)
 803            {
 804                return viewI.getMetrics();
 805            }
 806            return new Dictionary<string, IceMX.Metrics[]>();
 807        }
 808    }
 809
 810    public override IceMX.MetricsFailures[] getMapMetricsFailures(string view, string map, Ice.Current current)
 811    {
 812        lock (_mutex)
 813        {
 814            MetricsViewI viewI = getMetricsView(view);
 815            if (viewI is not null)
 816            {
 817                return viewI.getFailures(map);
 818            }
 819            return [];
 820        }
 821    }
 822
 823    public override IceMX.MetricsFailures getMetricsFailures(
 824        string view,
 825        string map,
 826        string id,
 827        Ice.Current current)
 828    {
 829        lock (_mutex)
 830        {
 831            MetricsViewI viewI = getMetricsView(view);
 832            if (viewI is not null)
 833            {
 834                return viewI.getFailures(map, id);
 835            }
 836            return new(failures: []);
 837        }
 838    }
 839
 840    public void registerMap<T>(string map, Action updater)
 841        where T : IceMX.Metrics, new()
 842    {
 843        bool updated;
 844        MetricsMapFactory<T> factory;
 845        lock (_mutex)
 846        {
 847            factory = new MetricsMapFactory<T>(updater);
 848            _factories.Add(map, factory);
 849            updated = addOrUpdateMap(map, factory);
 850        }
 851        if (updated)
 852        {
 853            factory.update();
 854        }
 855    }
 856
 857    public void registerSubMap<S>(string map, string subMap, System.Reflection.FieldInfo field)
 858        where S : IceMX.Metrics, new()
 859    {
 860        bool updated;
 861        IMetricsMapFactory factory;
 862        lock (_mutex)
 863        {
 864            if (!_factories.TryGetValue(map, out factory))
 865            {
 866                return;
 867            }
 868            factory.registerSubMap<S>(subMap, field);
 869            removeMap(map);
 870            updated = addOrUpdateMap(map, factory);
 871        }
 872        if (updated)
 873        {
 874            factory.update();
 875        }
 876    }
 877
 878    public void unregisterMap(string mapName)
 879    {
 880        bool updated;
 881        IMetricsMapFactory factory;
 882        lock (_mutex)
 883        {
 884            if (!_factories.TryGetValue(mapName, out factory))
 885            {
 886                return;
 887            }
 888            _factories.Remove(mapName);
 889            updated = removeMap(mapName);
 890        }
 891        if (updated)
 892        {
 893            factory.update();
 894        }
 895    }
 896
 897    public List<MetricsMap<T>> getMaps<T>(string mapName) where T : IceMX.Metrics, new()
 898    {
 899        var maps = new List<MetricsMap<T>>();
 900        foreach (MetricsViewI v in _views.Values)
 901        {
 902            MetricsMap<T> map = v.getMap<T>(mapName);
 903            if (map != null)
 904            {
 905                maps.Add(map);
 906            }
 907        }
 908        return maps;
 909    }
 910
 911    public Ice.Logger getLogger() => _logger;
 912
 913    internal void updated(Dictionary<string, string> changes)
 914    {
 915        foreach (KeyValuePair<string, string> e in changes)
 916        {
 917            if (e.Key.StartsWith("IceMX.", StringComparison.Ordinal))
 918            {
 919                // Update the metrics views using the new configuration.
 920                try
 921                {
 922                    updateViews();
 923                }
 924                catch (System.Exception ex)
 925                {
 926                    _logger.warning("unexpected exception while updating metrics view configuration:\n" +
 927                                    ex.ToString());
 928                }
 929                return;
 930            }
 931        }
 932    }
 933
 934    private MetricsViewI getMetricsView(string name)
 935    {
 936        if (!_views.TryGetValue(name, out MetricsViewI view))
 937        {
 938            if (!_disabledViews.Contains(name))
 939            {
 940                throw new IceMX.UnknownMetricsView();
 941            }
 942            return null;
 943        }
 944        return view;
 945    }
 946
 947    private bool addOrUpdateMap(string mapName, IMetricsMapFactory factory)
 948    {
 949        bool updated = false;
 950        foreach (MetricsViewI v in _views.Values)
 951        {
 952            updated |= v.addOrUpdateMap(_properties, mapName, factory, _logger);
 953        }
 954        return updated;
 955    }
 956
 957    private bool removeMap(string mapName)
 958    {
 959        bool updated = false;
 960        foreach (MetricsViewI v in _views.Values)
 961        {
 962            updated |= v.removeMap(mapName);
 963        }
 964        return updated;
 965    }
 966
 967    private readonly Ice.Properties _properties;
 968
 969    private readonly Ice.Logger _logger;
 970
 971    private readonly Dictionary<string, IMetricsMapFactory> _factories =
 972        new Dictionary<string, IMetricsMapFactory>();
 973
 974    private Dictionary<string, MetricsViewI> _views = new Dictionary<string, MetricsViewI>();
 975
 976    private readonly List<string> _disabledViews = new List<string>();
 977    private readonly object _mutex = new();
 978}