001package org.jpos.metrics.iso;
002
003import io.micrometer.core.instrument.MeterRegistry;
004import io.micrometer.core.instrument.Tags;
005import org.jpos.iso.ISOMsg;
006import org.jpos.metrics.MeterInfo;
007
008import java.util.ArrayList;
009import java.util.List;
010import java.util.stream.Collectors;
011
012/** Interface for ISO message metrics tracking. */
013public interface ISOMsgMetrics {
014    /** Default channel metric name. */
015    String DEFAULT_CHANNEL_METRIC_NAME = MeterInfo.ISOMSG_IN.id();
016
017    /** Environment variable name for channel tags configuration. */
018    String ENV_CHANNEL_TAGS =   "metrics.channel.tags";
019    /** Default tags for channel metrics. */
020    String DEFAULT_TAGS =       "name, type, direction";
021
022    /** Environment variable name for channel fields configuration. */
023    String ENV_CHANNEL_FIELDS =     "metrics.channel.fields";
024    /** Default channel fields for metrics. */
025    String DEFAULT_CHANNEL_FIELDS = "mti";
026
027    /** Implemented by components that expose an {@link ISOMsgMetrics} instance. */
028    interface Source{
029        /**
030         * Sets the ISOMsgMetrics instance.
031         * @param metrics the ISOMsgMetrics to attach
032         */
033        void setISOMsgMetrics(ISOMsgMetrics metrics);
034        /**
035         * Returns the attached ISOMsgMetrics.
036         * @return the ISOMsgMetrics, or null
037         */
038        ISOMsgMetrics getISOMsgMetrics();
039    }
040
041    /** Sets the metric name.
042     * @param metricName the metric name
043     */
044    void setMetricName(String metricName);
045    /** Returns the metric name.
046     * @return the metric name
047     */
048    String getMetricName();
049
050    /** Returns the metric description.
051     * @return the metric description
052     */
053    String getMetricDescription();
054    /** Sets the metric description.
055     * @param metricDescription the metric description
056     */
057    void setMetricDescription(String metricDescription);
058
059    /** Adds metric tags to the given Tags object.
060     * @param tags the Tags to add to
061     * @return the updated Tags
062     */
063    Tags addTags(Tags tags);
064    /** Adds metric tags from an array of strings.
065     * @param tags the tags to add
066     * @return the updated Tags
067     */
068    default Tags addTags(String ...tags) { return addTags(Tags.of(tags)); }
069    /** Returns all metric tags.
070     * @return the metric tags
071     */
072    default Tags getTags() { return addTags(Tags.empty()); }
073
074    /**
075     * Records an {@link ISOMsg} in the meter registry.<br>
076     * The metric name and tags will be taken strictly from this object's
077     * configuration. <br>
078     * If this object hasn't been successfully registered, it throws an
079     * {@link IllegalStateException}.
080     *
081     * @param m the {@link ISOMsg} to record.
082     * @throws IllegalStateException when this object hasn't been registered
083     */
084    void recordMessage(ISOMsg m) throws IllegalStateException;
085
086    /**
087     * Records an {@link ISOMsg} in the meter registry.<br>
088     * Similar to {@link #recordMessage(ISOMsg)} but using the metric name, description and maybe some
089     * tags taken from the {@link MeterInfo} argument.
090     * <p>
091     * If the metric for that combination of {@link MeterInfo} values and local values fails to register
092     * in the global {@link MeterRegistry} (or any underlying one like the Prometheus registry), the method
093     * may throw an {@link IllegalStateException}.  This also happens if this object hasn't been successfully
094     * registered by The metric name and tags will be taken from
095     * what has been configured. If this object hasn't been successfully registered, it throws an
096     * {@link IllegalStateException}.
097     *
098     * @param m          the ISO message
099     * @param meterInfo  the meter info to record
100     * @throws IllegalStateException if recording fails
101     */
102    void recordMessage(ISOMsg m, MeterInfo meterInfo) throws IllegalStateException;
103
104
105    /**
106     * Register this object to work with a given {@link MeterRegistry}.<br>
107     *
108     * This method may serve more than one purpose in the object's lifecycle:
109     * <ul>
110     *  <li>Assign a {@link MeterRegistry} to be used for the created meters.
111     *  (The registry can be obtained by calling {@link #getRegistry()})</li>
112     *  <li>Before this object has been registered, it can be configured by setting tags, etc.,
113     *      but attempting to record a message (e.g. through  {@link #recordMessage(ISOMsg)})
114     *      will throw an {@link IllegalStateException}.</li>
115     *  <li>After it has been registered, it's ready to record messages.
116     *      However, it <b>can't be configured</b> any longer, or it will throw an {@link IllegalStateException}.
117     *      The object's configuration can be considered "frozen".</li>
118     *  <li>In some (future) implementation, it may make use of the {@link #getMetricSignature()} to
119     *      do some caching to ensure that every metric name has only one set of tag keys, thus avoiding
120     *      metrics name+keyset collision in {@code PrometheusMeterRegistry}.</li>
121     * </ul>
122     *
123     *  The {@link #unregister()} method should be called when done using this object.
124     *
125     * @param registry the meter registry to register with
126     * @return true if successful, false if there was an error
127     */
128    boolean register(MeterRegistry registry);
129
130    /**
131     * It calls {@link #removeMeters()} and clears its internal reference to its {@link MeterRegistry}.<br>
132     *
133     * It will also "unfreeze" the object, making it available for reconfiguration.
134     */
135    void unregister();
136
137    /**
138     * Returns the meter registry.
139     * @return the MeterRegistry
140     */
141    MeterRegistry getRegistry();
142
143    /** Removes all registered meters. */
144    void removeMeters();
145
146
147    /**
148     * A unique meter signature, concatenating the meter name, vertical pipe, comma-separated sorted list of all tag keys
149     * that this object produces.<br>
150     * Default implementation uses the values from {@link #getMetricName()} and {@link #getTags()}.
151     * A concrete implementation must make sure of gathering all the appropriate tag keys from internal config state
152     * which may include more than what's returned by {@link #getTags()}.
153     * @return  The unique metric signature.
154     */
155    default String getMetricSignature() {
156        List<String> keys = new ArrayList<>();
157        getTags().forEach(t -> keys.add(t.getKey()));
158        return getMetricName()+"|"+
159                keys.stream().sorted().collect(Collectors.joining(","));
160    }
161}