001/* 002 * (C) Copyright 2013 Coda Hale <[email protected]> and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * 017 * This is a patched version of Coda Hale CsvReporter, the sanitizer method is overrided to enable 018 * metric name with a '/' in the name. 019 * 020 * @since 8.3 021 */ 022 023package org.nuxeo.runtime.metrics; 024 025import com.codahale.metrics.Clock; 026import com.codahale.metrics.Counter; 027import com.codahale.metrics.Gauge; 028import com.codahale.metrics.Histogram; 029import com.codahale.metrics.Meter; 030import com.codahale.metrics.MetricFilter; 031import com.codahale.metrics.MetricRegistry; 032import com.codahale.metrics.ScheduledReporter; 033import com.codahale.metrics.Snapshot; 034import com.codahale.metrics.Timer; 035 036import org.apache.commons.logging.Log; 037import org.apache.commons.logging.LogFactory; 038 039import java.io.*; 040import java.nio.charset.Charset; 041import java.util.Locale; 042import java.util.Map; 043import java.util.SortedMap; 044import java.util.concurrent.TimeUnit; 045 046 047/** 048 * A reporter which creates a comma-separated values file of the measurements for each metric. 049 */ 050public class CsvReporter extends ScheduledReporter { 051 /** 052 * Returns a new {@link Builder} for {@link CsvReporter}. 053 * 054 * @param registry the registry to report 055 * @return a {@link Builder} instance for a {@link CsvReporter} 056 */ 057 public static Builder forRegistry(MetricRegistry registry) { 058 return new Builder(registry); 059 } 060 061 062 /** 063 * A builder for {@link CsvReporter} instances. Defaults to using the default locale, converting 064 * rates to events/second, converting durations to milliseconds, and not filtering metrics. 065 */ 066 public static class Builder { 067 private final MetricRegistry registry; 068 private Locale locale; 069 private TimeUnit rateUnit; 070 private TimeUnit durationUnit; 071 private Clock clock; 072 private MetricFilter filter; 073 074 private Builder(MetricRegistry registry) { 075 this.registry = registry; 076 this.locale = Locale.getDefault(); 077 this.rateUnit = TimeUnit.SECONDS; 078 this.durationUnit = TimeUnit.MILLISECONDS; 079 this.clock = Clock.defaultClock(); 080 this.filter = MetricFilter.ALL; 081 } 082 083 /** 084 * Format numbers for the given {@link Locale}. 085 * 086 * @param locale a {@link Locale} 087 * @return {@code this} 088 */ 089 public Builder formatFor(Locale locale) { 090 this.locale = locale; 091 return this; 092 } 093 094 /** 095 * Convert rates to the given time unit. 096 * 097 * @param rateUnit a unit of time 098 * @return {@code this} 099 */ 100 public Builder convertRatesTo(TimeUnit rateUnit) { 101 this.rateUnit = rateUnit; 102 return this; 103 } 104 105 /** 106 * Convert durations to the given time unit. 107 * 108 * @param durationUnit a unit of time 109 * @return {@code this} 110 */ 111 public Builder convertDurationsTo(TimeUnit durationUnit) { 112 this.durationUnit = durationUnit; 113 return this; 114 } 115 116 /** 117 * Use the given {@link Clock} instance for the time. 118 * 119 * @param clock a {@link Clock} instance 120 * @return {@code this} 121 */ 122 public Builder withClock(Clock clock) { 123 this.clock = clock; 124 return this; 125 } 126 127 /** 128 * Only report metrics which match the given filter. 129 * 130 * @param filter a {@link MetricFilter} 131 * @return {@code this} 132 */ 133 public Builder filter(MetricFilter filter) { 134 this.filter = filter; 135 return this; 136 } 137 138 /** 139 * Builds a {@link CsvReporter} with the given properties, writing {@code .csv} files to the 140 * given directory. 141 * 142 * @param directory the directory in which the {@code .csv} files will be created 143 * @return a {@link CsvReporter} 144 */ 145 public CsvReporter build(File directory) { 146 return new CsvReporter(registry, 147 directory, 148 locale, 149 rateUnit, 150 durationUnit, 151 clock, 152 filter); 153 } 154 } 155 156 private static final Log LOGGER = LogFactory.getLog(CsvReporter.class); 157 private static final Charset UTF_8 = Charset.forName("UTF-8"); 158 159 private final File directory; 160 private final Locale locale; 161 private final Clock clock; 162 163 private CsvReporter(MetricRegistry registry, 164 File directory, 165 Locale locale, 166 TimeUnit rateUnit, 167 TimeUnit durationUnit, 168 Clock clock, 169 MetricFilter filter) { 170 super(registry, "csv-reporter", filter, rateUnit, durationUnit); 171 this.directory = directory; 172 this.locale = locale; 173 this.clock = clock; 174 } 175 176 177 @Override 178 public void report(SortedMap<String, Gauge> gauges, 179 SortedMap<String, Counter> counters, 180 SortedMap<String, Histogram> histograms, 181 SortedMap<String, Meter> meters, 182 SortedMap<String, Timer> timers) { 183 final long timestamp = TimeUnit.MILLISECONDS.toSeconds(clock.getTime()); 184 185 for (Map.Entry<String, Gauge> entry : gauges.entrySet()) { 186 reportGauge(timestamp, entry.getKey(), entry.getValue()); 187 } 188 189 for (Map.Entry<String, Counter> entry : counters.entrySet()) { 190 reportCounter(timestamp, entry.getKey(), entry.getValue()); 191 } 192 193 for (Map.Entry<String, Histogram> entry : histograms.entrySet()) { 194 reportHistogram(timestamp, entry.getKey(), entry.getValue()); 195 } 196 197 for (Map.Entry<String, Meter> entry : meters.entrySet()) { 198 reportMeter(timestamp, entry.getKey(), entry.getValue()); 199 } 200 201 for (Map.Entry<String, Timer> entry : timers.entrySet()) { 202 reportTimer(timestamp, entry.getKey(), entry.getValue()); 203 } 204 } 205 206 private void reportTimer(long timestamp, String name, Timer timer) { 207 final Snapshot snapshot = timer.getSnapshot(); 208 209 report(timestamp, 210 name, 211 "count,max,mean,min,stddev,p50,p75,p95,p98,p99,p999,mean_rate,m1_rate,m5_rate,m15_rate,rate_unit,duration_unit", 212 "%d,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,calls/%s,%s", 213 timer.getCount(), 214 convertDuration(snapshot.getMax()), 215 convertDuration(snapshot.getMean()), 216 convertDuration(snapshot.getMin()), 217 convertDuration(snapshot.getStdDev()), 218 convertDuration(snapshot.getMedian()), 219 convertDuration(snapshot.get75thPercentile()), 220 convertDuration(snapshot.get95thPercentile()), 221 convertDuration(snapshot.get98thPercentile()), 222 convertDuration(snapshot.get99thPercentile()), 223 convertDuration(snapshot.get999thPercentile()), 224 convertRate(timer.getMeanRate()), 225 convertRate(timer.getOneMinuteRate()), 226 convertRate(timer.getFiveMinuteRate()), 227 convertRate(timer.getFifteenMinuteRate()), 228 getRateUnit(), 229 getDurationUnit()); 230 } 231 232 private void reportMeter(long timestamp, String name, Meter meter) { 233 report(timestamp, 234 name, 235 "count,mean_rate,m1_rate,m5_rate,m15_rate,rate_unit", 236 "%d,%f,%f,%f,%f,events/%s", 237 meter.getCount(), 238 convertRate(meter.getMeanRate()), 239 convertRate(meter.getOneMinuteRate()), 240 convertRate(meter.getFiveMinuteRate()), 241 convertRate(meter.getFifteenMinuteRate()), 242 getRateUnit()); 243 } 244 245 private void reportHistogram(long timestamp, String name, Histogram histogram) { 246 final Snapshot snapshot = histogram.getSnapshot(); 247 248 report(timestamp, 249 name, 250 "count,max,mean,min,stddev,p50,p75,p95,p98,p99,p999", 251 "%d,%d,%f,%d,%f,%f,%f,%f,%f,%f,%f", 252 histogram.getCount(), 253 snapshot.getMax(), 254 snapshot.getMean(), 255 snapshot.getMin(), 256 snapshot.getStdDev(), 257 snapshot.getMedian(), 258 snapshot.get75thPercentile(), 259 snapshot.get95thPercentile(), 260 snapshot.get98thPercentile(), 261 snapshot.get99thPercentile(), 262 snapshot.get999thPercentile()); 263 } 264 265 private void reportCounter(long timestamp, String name, Counter counter) { 266 report(timestamp, name, "count", "%d", counter.getCount()); 267 } 268 269 private void reportGauge(long timestamp, String name, Gauge gauge) { 270 report(timestamp, name, "value", "%s", gauge.getValue()); 271 } 272 273 private void report(long timestamp, String name, String header, String line, Object... values) { 274 try { 275 final File file = new File(directory, sanitize(name) + ".csv"); 276 final boolean fileAlreadyExists = file.exists(); 277 if (fileAlreadyExists || file.createNewFile()) { 278 final PrintWriter out = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file,true), UTF_8)); 279 try { 280 if (!fileAlreadyExists) { 281 out.println("t," + header); 282 } 283 out.printf(locale, String.format(locale, "%d,%s%n", timestamp, line), values); 284 } finally { 285 out.close(); 286 } 287 } 288 } catch (IOException e) { 289 LOGGER.warn("Error writing to " + name, e); 290 } 291 } 292 293 protected String sanitize(String name) { 294 return name.replace('/', '.'); 295 } 296}