001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *      http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.oozie.util;
020
021import com.google.common.collect.Maps;
022import org.apache.hadoop.conf.Configuration;
023import org.apache.oozie.action.hadoop.PasswordMasker;
024import org.apache.oozie.service.ConfigurationService;
025import org.apache.oozie.service.Services;
026
027import java.util.ArrayList;
028import java.util.Collection;
029import java.util.Collections;
030import java.util.HashMap;
031import java.util.LinkedHashMap;
032import java.util.LinkedHashSet;
033import java.util.List;
034import java.util.Map;
035import java.util.Set;
036import java.util.concurrent.ConcurrentHashMap;
037import java.util.concurrent.ScheduledExecutorService;
038import java.util.concurrent.TimeUnit;
039import java.util.concurrent.atomic.AtomicLong;
040import java.util.concurrent.locks.Lock;
041import java.util.concurrent.locks.ReentrantLock;
042
043/**
044 * Instrumentation framework that supports Timers, Counters, Variables and Sampler instrumentation elements. <p> All
045 * instrumentation elements have a group and a name.
046 * @deprecated since 5.0.0
047 */
048@Deprecated
049public class Instrumentation {
050    private ScheduledExecutorService scheduler;
051    private Lock counterLock;
052    private Lock timerLock;
053    private Lock variableLock;
054    private Lock samplerLock;
055    private Map<String, Map<String, Map<String, Object>>> all;
056    private Map<String, Map<String, Element<Long>>> counters;
057    private Map<String, Map<String, Element<Timer>>> timers;
058    private Map<String, Map<String, Element<Variable>>> variables;
059    private Map<String, Map<String, Element<Double>>> samplers;
060
061    /**
062     * Instrumentation constructor.
063     */
064    @SuppressWarnings("unchecked")
065    public Instrumentation() {
066        counterLock = new ReentrantLock();
067        timerLock = new ReentrantLock();
068        variableLock = new ReentrantLock();
069        samplerLock = new ReentrantLock();
070        all = new LinkedHashMap<String, Map<String, Map<String, Object>>>();
071        counters = new ConcurrentHashMap<String, Map<String, Element<Long>>>();
072        timers = new ConcurrentHashMap<String, Map<String, Element<Timer>>>();
073        variables = new ConcurrentHashMap<String, Map<String, Element<Variable>>>();
074        samplers = new ConcurrentHashMap<String, Map<String, Element<Double>>>();
075        all.put("variables", (Map<String, Map<String, Object>>) (Object) variables);
076        all.put("samplers", (Map<String, Map<String, Object>>) (Object) samplers);
077        all.put("counters", (Map<String, Map<String, Object>>) (Object) counters);
078        all.put("timers", (Map<String, Map<String, Object>>) (Object) timers);
079    }
080
081    /**
082     * Set the scheduler instance to handle the samplers.
083     *
084     * @param scheduler scheduler instance.
085     */
086    public void setScheduler(ScheduledExecutorService scheduler) {
087        this.scheduler = scheduler;
088    }
089
090    /**
091     * Cron is a stopwatch that can be started/stopped several times. <p> This class is not thread safe, it does not
092     * need to be. <p> It keeps track of the total time (first start to last stop) and the running time (total time
093     * minus the stopped intervals). <p> Once a Cron is complete it must be added to the corresponding group/name in a
094     * Instrumentation instance.
095     */
096    public static class Cron {
097        private long start;
098        private long end;
099        private long lapStart;
100        private long own;
101        private long total;
102        private boolean running;
103
104        /**
105         * Creates new Cron, stopped, in zero.
106         */
107        public Cron() {
108            running = false;
109        }
110
111        /**
112         * Start the cron. It cannot be already started.
113         */
114        public void start() {
115            if (!running) {
116                if (lapStart == 0) {
117                    lapStart = System.currentTimeMillis();
118                    if (start == 0) {
119                        start = lapStart;
120                        end = start;
121                    }
122                }
123                running = true;
124            }
125        }
126
127        /**
128         * Stops the cron. It cannot be already stopped.
129         */
130        public void stop() {
131            if (running) {
132                end = System.currentTimeMillis();
133                if (start == 0) {
134                    start = end;
135                }
136                total = end - start;
137                if (lapStart > 0) {
138                    own += end - lapStart;
139                    lapStart = 0;
140                }
141                running = false;
142            }
143        }
144
145        /**
146         * Return the start time of the cron. It must be stopped.
147         *
148         * @return the start time of the cron.
149         */
150        public long getStart() {
151            if (running) {
152                throw new IllegalStateException("Timer running");
153            }
154            return start;
155        }
156
157        /**
158         * Return the end time of the cron.  It must be stopped.
159         *
160         * @return the end time of the cron.
161         */
162        public long getEnd() {
163            if (running) {
164                throw new IllegalStateException("Timer running");
165            }
166            return end;
167        }
168
169        /**
170         * Return the total time of the cron. It must be stopped.
171         *
172         * @return the total time of the cron.
173         */
174        public long getTotal() {
175            if (running) {
176                throw new IllegalStateException("Timer running");
177            }
178            return total;
179        }
180
181        /**
182         * Return the own time of the cron. It must be stopped.
183         *
184         * @return the own time of the cron.
185         */
186        public long getOwn() {
187            if (running) {
188                throw new IllegalStateException("Timer running");
189            }
190            return own;
191        }
192
193    }
194
195    /**
196     * Gives access to a snapshot of an Instrumentation element (Counter, Timer). <p> Instrumentation element snapshots
197     * are returned by the {@link Instrumentation#getCounters()} and {@link Instrumentation#getTimers()} ()} methods.
198     */
199    public interface Element<T> {
200
201        /**
202         * Return the snapshot value of the Intrumentation element.
203         *
204         * @return the snapshot value of the Intrumentation element.
205         */
206        T getValue();
207    }
208
209    /**
210     * Counter Instrumentation element.
211     */
212    public static class Counter extends AtomicLong implements Element<Long> {
213
214        /**
215         * Return the counter snapshot.
216         *
217         * @return the counter snapshot.
218         */
219        public Long getValue() {
220            return get();
221        }
222
223        /**
224         * Return the String representation of the counter value.
225         *
226         * @return the String representation of the counter value.
227         */
228        public String toString() {
229            return Long.toString(get());
230        }
231
232    }
233
234    /**
235     * Timer Instrumentation element.
236     */
237    public static class Timer implements Element<Timer> {
238        Lock lock = new ReentrantLock();
239        private long ownTime;
240        private long totalTime;
241        private long ticks;
242        private long ownSquareTime;
243        private long totalSquareTime;
244        private long ownMinTime;
245        private long ownMaxTime;
246        private long totalMinTime;
247        private long totalMaxTime;
248
249        /**
250         * Timer constructor. <p> It is project private for test purposes.
251         */
252        Timer() {
253        }
254
255        /**
256         * Return the String representation of the timer value.
257         *
258         * @return the String representation of the timer value.
259         */
260        public String toString() {
261            return XLog.format("ticks[{0}] totalAvg[{1}] ownAvg[{2}]", ticks, getTotalAvg(), getOwnAvg());
262        }
263
264        /**
265         * Return the timer snapshot.
266         *
267         * @return the timer snapshot.
268         */
269        public Timer getValue() {
270            try {
271                lock.lock();
272                Timer timer = new Timer();
273                timer.ownTime = ownTime;
274                timer.totalTime = totalTime;
275                timer.ticks = ticks;
276                timer.ownSquareTime = ownSquareTime;
277                timer.totalSquareTime = totalSquareTime;
278                timer.ownMinTime = ownMinTime;
279                timer.ownMaxTime = ownMaxTime;
280                timer.totalMinTime = totalMinTime;
281                timer.totalMaxTime = totalMaxTime;
282                return timer;
283            }
284            finally {
285                lock.unlock();
286            }
287        }
288
289        /**
290         * Add a cron to a timer. <p> It is project private for test purposes.
291         *
292         * @param cron Cron to add.
293         */
294        void addCron(Cron cron) {
295            try {
296                lock.lock();
297                long own = cron.getOwn();
298                long total = cron.getTotal();
299                ownTime += own;
300                totalTime += total;
301                ticks++;
302                ownSquareTime += own * own;
303                totalSquareTime += total * total;
304                if (ticks == 1) {
305                    ownMinTime = own;
306                    ownMaxTime = own;
307                    totalMinTime = total;
308                    totalMaxTime = total;
309                }
310                else {
311                    ownMinTime = Math.min(ownMinTime, own);
312                    ownMaxTime = Math.max(ownMaxTime, own);
313                    totalMinTime = Math.min(totalMinTime, total);
314                    totalMaxTime = Math.max(totalMaxTime, total);
315                }
316            }
317            finally {
318                lock.unlock();
319            }
320        }
321
322        /**
323         * Return the own accumulated computing time by the timer.
324         *
325         * @return own accumulated computing time by the timer.
326         */
327        public long getOwn() {
328            return ownTime;
329        }
330
331        /**
332         * Return the total accumulated computing time by the timer.
333         *
334         * @return total accumulated computing time by the timer.
335         */
336        public long getTotal() {
337            return totalTime;
338        }
339
340        /**
341         * Return the number of times a cron was added to the timer.
342         *
343         * @return the number of times a cron was added to the timer.
344         */
345        public long getTicks() {
346            return ticks;
347        }
348
349        /**
350         * Return the sum of the square own times. <p> It can be used to calculate the standard deviation.
351         *
352         * @return the sum of the square own timer.
353         */
354        public long getOwnSquareSum() {
355            return ownSquareTime;
356        }
357
358        /**
359         * Return the sum of the square total times. <p> It can be used to calculate the standard deviation.
360         *
361         * @return the sum of the square own timer.
362         */
363        public long getTotalSquareSum() {
364            return totalSquareTime;
365        }
366
367        /**
368         * Returns the own minimum time.
369         *
370         * @return the own minimum time.
371         */
372        public long getOwnMin() {
373            return ownMinTime;
374        }
375
376        /**
377         * Returns the own maximum time.
378         *
379         * @return the own maximum time.
380         */
381        public long getOwnMax() {
382            return ownMaxTime;
383        }
384
385        /**
386         * Returns the total minimum time.
387         *
388         * @return the total minimum time.
389         */
390        public long getTotalMin() {
391            return totalMinTime;
392        }
393
394        /**
395         * Returns the total maximum time.
396         *
397         * @return the total maximum time.
398         */
399        public long getTotalMax() {
400            return totalMaxTime;
401        }
402
403        /**
404         * Returns the own average time.
405         *
406         * @return the own average time.
407         */
408        public long getOwnAvg() {
409            return (ticks != 0) ? ownTime / ticks : 0;
410        }
411
412        /**
413         * Returns the total average time.
414         *
415         * @return the total average time.
416         */
417        public long getTotalAvg() {
418            return (ticks != 0) ? totalTime / ticks : 0;
419        }
420
421        /**
422         * Returns the total time standard deviation.
423         *
424         * @return the total time standard deviation.
425         */
426        public double getTotalStdDev() {
427            return evalStdDev(ticks, totalTime, totalSquareTime);
428        }
429
430        /**
431         * Returns the own time standard deviation.
432         *
433         * @return the own time standard deviation.
434         */
435        public double getOwnStdDev() {
436            return evalStdDev(ticks, ownTime, ownSquareTime);
437        }
438
439        private double evalStdDev(long n, long sn, long ssn) {
440            return (n < 2) ? -1 : Math.sqrt((n * ssn - sn * sn) / (n * (n - 1)));
441        }
442
443    }
444
445    /**
446     * Add a cron to an instrumentation timer. The timer is created if it does not exists. <p> This method is thread
447     * safe.
448     *
449     * @param group timer group.
450     * @param name timer name.
451     * @param cron cron to add to the timer.
452     */
453    public void addCron(String group, String name, Cron cron) {
454        Map<String, Element<Timer>> map = timers.get(group);
455        if (map == null) {
456            try {
457                timerLock.lock();
458                map = timers.get(group);
459                if (map == null) {
460                    map = new HashMap<String, Element<Timer>>();
461                    timers.put(group, map);
462                }
463            }
464            finally {
465                timerLock.unlock();
466            }
467        }
468        Timer timer = (Timer) map.get(name);
469        if (timer == null) {
470            try {
471                timerLock.lock();
472                timer = (Timer) map.get(name);
473                if (timer == null) {
474                    timer = new Timer();
475                    map.put(name, timer);
476                }
477            }
478            finally {
479                timerLock.unlock();
480            }
481        }
482        timer.addCron(cron);
483    }
484
485    /**
486     * Increment an instrumentation counter. The counter is created if it does not exists. <p> This method is thread
487     * safe.
488     *
489     * @param group counter group.
490     * @param name counter name.
491     * @param count increment to add to the counter.
492     */
493    public void incr(String group, String name, long count) {
494        Map<String, Element<Long>> map = counters.get(group);
495        if (map == null) {
496            try {
497                counterLock.lock();
498                map = counters.get(group);
499                if (map == null) {
500                    map = new HashMap<String, Element<Long>>();
501                    counters.put(group, map);
502                }
503            }
504            finally {
505                counterLock.unlock();
506            }
507        }
508        Counter counter = (Counter) map.get(name);
509        if (counter == null) {
510            try {
511                counterLock.lock();
512                counter = (Counter) map.get(name);
513                if (counter == null) {
514                    counter = new Counter();
515                    map.put(name, counter);
516                }
517            }
518            finally {
519                counterLock.unlock();
520            }
521        }
522        counter.addAndGet(count);
523    }
524
525    /**
526     * Decrement an instrumentation counter. The counter is created if it does not exists. <p> This method is thread
527     * safe.
528     *
529     * @param group counter group.
530     * @param name counter name.
531     * @param count decrement to add to the counter.
532     */
533    public void decr(final String group, final String name, final long count) {
534        incr(group, name, -count);
535    }
536
537    /**
538     * Interface for instrumentation variables. <p> For example a the database service could expose the number of
539     * currently active connections.
540     */
541    public interface Variable<T> extends Element<T> {
542    }
543
544    /**
545     * Add an instrumentation variable. The variable must not exist. <p> This method is thread safe.
546     *
547     * @param group counter group.
548     * @param name counter name.
549     * @param variable variable to add.
550     */
551    @SuppressWarnings("unchecked")
552    public void addVariable(String group, String name, Variable variable) {
553        Map<String, Element<Variable>> map = variables.get(group);
554        if (map == null) {
555            try {
556                variableLock.lock();
557                map = variables.get(group);
558                if (map == null) {
559                    map = new HashMap<String, Element<Variable>>();
560                    variables.put(group, map);
561                }
562            }
563            finally {
564                variableLock.unlock();
565            }
566        }
567        if (map.containsKey(name)) {
568            throw new RuntimeException(XLog.format("Variable group=[{0}] name=[{1}] already defined", group, name));
569        }
570        map.put(name, variable);
571    }
572
573    /**
574     * Return the JVM system properties.
575     *
576     * @return JVM system properties.
577     */
578    public Map<String, String> getJavaSystemProperties() {
579        Map<String, String> unmasked = Maps.fromProperties(System.getProperties());
580        return new PasswordMasker().mask(unmasked);
581    }
582
583    /**
584     * Return the OS environment used to start Oozie.
585     *
586     * @return the OS environment used to start Oozie.
587     */
588    public Map<String, String> getOSEnv() {
589        Map<String, String> unmasked = System.getenv();
590        return new PasswordMasker().mask(unmasked);
591    }
592
593    /**
594     * Return the current system configuration as a Map&lt;String,String&gt;.
595     *
596     * @return the current system configuration as a Map&lt;String,String&gt;.
597     */
598    public Map<String, String> getConfiguration() {
599        final Configuration maskedConf = Services.get().get(ConfigurationService.class).getMaskedConfiguration();
600
601        return new Map<String, String>() {
602            public int size() {
603                return maskedConf.size();
604            }
605
606            public boolean isEmpty() {
607                return maskedConf.size() == 0;
608            }
609
610            public boolean containsKey(Object o) {
611                return maskedConf.get((String) o) != null;
612            }
613
614            public boolean containsValue(Object o) {
615                throw new UnsupportedOperationException();
616            }
617
618            public String get(Object o) {
619                return maskedConf.get((String) o);
620            }
621
622            public String put(String s, String s1) {
623                throw new UnsupportedOperationException();
624            }
625
626            public String remove(Object o) {
627                throw new UnsupportedOperationException();
628            }
629
630            public void putAll(Map<? extends String, ? extends String> map) {
631                throw new UnsupportedOperationException();
632            }
633
634            public void clear() {
635                throw new UnsupportedOperationException();
636            }
637
638            public Set<String> keySet() {
639                Set<String> set = new LinkedHashSet<String>();
640                for (Entry<String, String> entry : maskedConf) {
641                    set.add(entry.getKey());
642                }
643                return set;
644            }
645
646            public Collection<String> values() {
647                Set<String> set = new LinkedHashSet<String>();
648                for (Entry<String, String> entry : maskedConf) {
649                    set.add(entry.getValue());
650                }
651                return set;
652            }
653
654            public Set<Entry<String, String>> entrySet() {
655                Set<Entry<String, String>> set = new LinkedHashSet<Entry<String, String>>();
656                for (Entry<String, String> entry : maskedConf) {
657                    set.add(entry);
658                }
659                return set;
660            }
661        };
662    }
663
664    /**
665     * Return all the counters. <p> This method is thread safe. <p> The counters are live. The counter value is a
666     * snapshot at the time the {@link Instrumentation.Element#getValue()} is invoked.
667     *
668     * @return all counters.
669     */
670    public Map<String, Map<String, Element<Long>>> getCounters() {
671        return counters;
672    }
673
674    /**
675     * Return all the timers. <p> This method is thread safe. <p> The timers are live. Once a timer is obtained, all
676     * its values are consistent (they are snapshot at the time the {@link Instrumentation.Element#getValue()} is
677     * invoked.
678     *
679     * @return all counters.
680     */
681    public Map<String, Map<String, Element<Timer>>> getTimers() {
682        return timers;
683    }
684
685    /**
686     * Return all the variables. <p> This method is thread safe. <p> The variables are live. The variable value is a
687     * snapshot at the time the {@link Instrumentation.Element#getValue()} is invoked.
688     *
689     * @return all counters.
690     */
691    public Map<String, Map<String, Element<Variable>>> getVariables() {
692        return variables;
693    }
694
695    /**
696     * Return a map containing all variables, counters and timers.
697     *
698     * @return a map containing all variables, counters and timers.
699     */
700    public Map<String, Map<String, Map<String, Object>>> getAll() {
701        return all;
702    }
703
704    /**
705     * Return the string representation of the instrumentation.
706     *
707     * @return the string representation of the instrumentation.
708     */
709    public String toString() {
710        String E = System.getProperty("line.separator");
711        StringBuilder sb = new StringBuilder(4096);
712        for (String element : all.keySet()) {
713            sb.append(element).append(':').append(E);
714            List<String> groups = new ArrayList<String>(all.get(element).keySet());
715            Collections.sort(groups);
716            for (String group : groups) {
717                sb.append("  ").append(group).append(':').append(E);
718                List<String> names = new ArrayList<String>(all.get(element).get(group).keySet());
719                Collections.sort(names);
720                for (String name : names) {
721                    sb.append("    ").append(name).append(": ").append(((Element) all.get(element).
722                            get(group).get(name)).getValue()).append(E);
723                }
724            }
725        }
726        return sb.toString();
727    }
728
729    private static class Sampler implements Element<Double>, Runnable {
730        private Lock lock = new ReentrantLock();
731        private int samplingInterval;
732        private Variable<Long> variable;
733        private long[] values;
734        private int current;
735        private long valuesSum;
736        private double rate;
737
738        public Sampler(int samplingPeriod, int samplingInterval, Variable<Long> variable) {
739            this.samplingInterval = samplingInterval;
740            this.variable = variable;
741            values = new long[samplingPeriod / samplingInterval];
742            valuesSum = 0;
743            current = -1;
744        }
745
746        public int getSamplingInterval() {
747            return samplingInterval;
748        }
749
750        public void run() {
751            try {
752                lock.lock();
753                long newValue = variable.getValue();
754                if (current == -1) {
755                    valuesSum = newValue;
756                    current = 0;
757                    values[current] = newValue;
758                }
759                else {
760                    current = (current + 1) % values.length;
761                    valuesSum = valuesSum - values[current] + newValue;
762                    values[current] = newValue;
763                }
764                rate = ((double) valuesSum) / values.length;
765            }
766            finally {
767                lock.unlock();
768            }
769        }
770
771        public Double getValue() {
772            return rate;
773        }
774    }
775
776    /**
777     * Add a sampling variable. <p> This method is thread safe.
778     *
779     * @param group timer group.
780     * @param name timer name.
781     * @param period sampling period to compute rate.
782     * @param interval sampling frequency, how often the variable is probed.
783     * @param variable variable to sample.
784     */
785    public void addSampler(String group, String name, int period, int interval, Variable<Long> variable) {
786        if (scheduler == null) {
787            throw new IllegalStateException("scheduler not set, cannot sample");
788        }
789        try {
790            samplerLock.lock();
791            Map<String, Element<Double>> map = samplers.get(group);
792            if (map == null) {
793                map = samplers.get(group);
794                if (map == null) {
795                    map = new HashMap<String, Element<Double>>();
796                    samplers.put(group, map);
797                }
798            }
799            if (map.containsKey(name)) {
800                throw new RuntimeException(XLog.format("Sampler group=[{0}] name=[{1}] already defined", group, name));
801            }
802            else {
803                Sampler sampler = new Sampler(period, interval, variable);
804                map.put(name, sampler);
805                scheduler.scheduleAtFixedRate(sampler, 0, sampler.getSamplingInterval(), TimeUnit.SECONDS);
806            }
807        }
808        finally {
809            samplerLock.unlock();
810        }
811    }
812
813    /**
814     * Return all the samplers. <p> This method is thread safe. <p> The samplers are live. The sampler value is a
815     * snapshot at the time the {@link Instrumentation.Element#getValue()} is invoked.
816     *
817     * @return all counters.
818     */
819    public Map<String, Map<String, Element<Double>>> getSamplers() {
820        return samplers;
821    }
822
823    public void stop() {
824
825    }
826
827}