blob: b471dec64539fa4c22c3c27ce3b00c6f938c233a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.jackrabbit.oak.plugins.blob;
import java.util.concurrent.TimeUnit;
import javax.management.openmbean.CompositeData;
import org.apache.jackrabbit.api.stats.TimeSeries;
import org.apache.jackrabbit.oak.commons.jmx.AnnotatedStandardMBean;
import org.apache.jackrabbit.oak.spi.blob.stats.BlobStoreStatsMBean;
import org.apache.jackrabbit.oak.spi.blob.stats.BlobStatsCollector;
import org.apache.jackrabbit.oak.stats.HistogramStats;
import org.apache.jackrabbit.oak.stats.MeterStats;
import org.apache.jackrabbit.oak.stats.StatisticsProvider;
import org.apache.jackrabbit.oak.stats.StatsOptions;
import org.apache.jackrabbit.stats.TimeSeriesAverage;
import org.apache.jackrabbit.stats.TimeSeriesStatsUtil;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.apache.jackrabbit.oak.commons.IOUtils.humanReadableByteCount;
@SuppressWarnings("Duplicates")
public class BlobStoreStats extends AnnotatedStandardMBean implements BlobStoreStatsMBean, BlobStatsCollector {
private final Logger opsLogger = LoggerFactory.getLogger("org.apache.jackrabbit.oak.operations.blobs");
private static final String BLOB_DOWNLOAD_COUNT = "BLOB_DOWNLOAD_COUNT";
private static final String BLOB_UPLOADS = "BLOB_UPLOADS";
private static final String BLOB_DOWNLOADS = "BLOB_DOWNLOADS";
private static final String BLOB_UPLOAD_COUNT = "BLOB_UPLOAD_COUNT";
private final StatisticsProvider statisticsProvider;
private final HistogramStats uploadHisto;
private final MeterStats uploadCount;
private final MeterStats uploadSizeSeries;
private final MeterStats uploadTimeSeries;
private final TimeSeries uploadRateSeries;
private final HistogramStats downloadHisto;
private final MeterStats downloadCount;
private final MeterStats downloadSizeSeries;
private final MeterStats downloadTimeSeries;
private final TimeSeries downloadRateSeries;
private final TimeUnit recordedTimeUnit = TimeUnit.NANOSECONDS;
public BlobStoreStats(@NotNull StatisticsProvider sp) {
super(BlobStoreStatsMBean.class);
this.statisticsProvider = checkNotNull(sp);
this.uploadHisto = sp.getHistogram(BLOB_UPLOADS, StatsOptions.DEFAULT);
this.uploadCount = sp.getMeter(BLOB_UPLOAD_COUNT, StatsOptions.DEFAULT);
this.uploadSizeSeries = sp.getMeter("BLOB_UPLOAD_SIZE", StatsOptions.TIME_SERIES_ONLY);
this.uploadTimeSeries = sp.getMeter("BLOB_UPLOAD_TIME", StatsOptions.TIME_SERIES_ONLY);
this.uploadRateSeries = getAvgTimeSeries("BLOB_UPLOAD_SIZE", "BLOB_UPLOAD_TIME");
this.downloadHisto = sp.getHistogram(BLOB_DOWNLOADS, StatsOptions.DEFAULT);
this.downloadCount = sp.getMeter(BLOB_DOWNLOAD_COUNT, StatsOptions.DEFAULT);
this.downloadSizeSeries = sp.getMeter("BLOB_DOWNLOAD_SIZE", StatsOptions.TIME_SERIES_ONLY);
this.downloadTimeSeries = sp.getMeter("BLOB_DOWNLOAD_TIME", StatsOptions.TIME_SERIES_ONLY);
this.downloadRateSeries = getAvgTimeSeries("BLOB_DOWNLOAD_SIZE", "BLOB_DOWNLOAD_TIME");
}
@Override
public void uploaded(long timeTaken, TimeUnit unit, long size) {
uploadHisto.update(size);
//Recording upload like this is not accurate. A more accurate way
//would be to mark as upload or download is progressing.
//That would however add quite a bit of overhead
//Approach below would record an upload/download at moment when
//it got completed. So acts like a rough approximation
uploadSizeSeries.mark(size);
uploadTimeSeries.mark(recordedTimeUnit.convert(timeTaken, unit));
opsLogger.debug("Uploaded {} bytes in {} ms", size, unit.toMillis(timeTaken));
}
@Override
public void downloaded(String blobId, long timeTaken, TimeUnit unit, long size) {
downloadHisto.update(size);
downloadSizeSeries.mark(size);
downloadTimeSeries.mark(recordedTimeUnit.convert(timeTaken, unit));
opsLogger.debug("Downloaded {} - {} bytes in {} ms", blobId, size, unit.toMillis(timeTaken));
}
@Override
public void uploadCompleted(String blobId) {
uploadCount.mark();
opsLogger.debug("Upload completed - {}", blobId);
}
@Override
public void downloadCompleted(String blobId) {
downloadCount.mark();
opsLogger.debug("Download completed - {}", blobId);
}
//~--------------------------------------< BlobStoreMBean >
@Override
public long getUploadTotalSize() {
return uploadSizeSeries.getCount();
}
@Override
public long getUploadCount() {
return uploadCount.getCount();
}
@Override
public long getUploadTotalSeconds() {
return recordedTimeUnit.toSeconds(uploadTimeSeries.getCount());
}
@Override
public long getDownloadTotalSize() {
return downloadSizeSeries.getCount();
}
@Override
public long getDownloadCount() {
return downloadCount.getCount();
}
@Override
public long getDownloadTotalSeconds() {
return recordedTimeUnit.toSeconds(downloadTimeSeries.getCount());
}
@Override
public String blobStoreInfoAsString() {
return String.format("Uploads - size = %s, count = %d%nDownloads - size = %s, count = %d",
humanReadableByteCount(getUploadTotalSize()),
getUploadCount(),
humanReadableByteCount(getDownloadTotalSize()),
getDownloadCount()
);
}
@Override
public CompositeData getUploadSizeHistory() {
return getTimeSeriesData(BLOB_UPLOADS, "Blob Uploads (bytes)");
}
@Override
public CompositeData getDownloadSizeHistory() {
return getTimeSeriesData(BLOB_DOWNLOADS, "Blob Downloads (bytes)");
}
@Override
public CompositeData getUploadRateHistory() {
return TimeSeriesStatsUtil.asCompositeData(uploadRateSeries, "Blob uploads bytes/secs");
}
@Override
public CompositeData getDownloadRateHistory() {
return TimeSeriesStatsUtil.asCompositeData(downloadRateSeries, "Blob downloads bytes/secs");
}
@Override
public CompositeData getUploadCountHistory() {
return getTimeSeriesData(BLOB_UPLOAD_COUNT, "Blob Upload Counts");
}
@Override
public CompositeData getDownloadCountHistory() {
return getTimeSeriesData(BLOB_DOWNLOAD_COUNT, "Blob Download Counts");
}
private CompositeData getTimeSeriesData(String name, String desc){
return TimeSeriesStatsUtil.asCompositeData(getTimeSeries(name), desc);
}
private TimeSeries getTimeSeries(String name) {
return statisticsProvider.getStats().getTimeSeries(name, true);
}
private TimeSeries getAvgTimeSeries(String nameValue, String nameCounter){
return new TimeSeriesAverage(getTimeSeries(nameValue),
new UnitConvertingTimeSeries(getTimeSeries(nameCounter), recordedTimeUnit, TimeUnit.SECONDS));
}
/**
* TimeSeries which converts a Nanonsecond based time to Seconds for
* calculating bytes/sec rate for upload and download
*/
private static class UnitConvertingTimeSeries implements TimeSeries {
private final TimeSeries series;
private final TimeUnit source;
private final TimeUnit dest;
public UnitConvertingTimeSeries(TimeSeries series, TimeUnit source, TimeUnit dest) {
this.series = series;
this.source = source;
this.dest = dest;
}
@Override
public long[] getValuePerSecond() {
return convert(series.getValuePerSecond());
}
@Override
public long[] getValuePerMinute() {
return convert(series.getValuePerMinute());
}
@Override
public long[] getValuePerHour() {
return convert(series.getValuePerHour());
}
@Override
public long[] getValuePerWeek() {
return convert(series.getValuePerWeek());
}
@Override
public long getMissingValue() {
return 0;
}
private long[] convert(long[] timings){
for (int i = 0; i < timings.length; i++) {
timings[i] = dest.convert(timings[i], source);
}
return timings;
}
}
}