1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.bedstead.metricsrecorder; 18 19 import android.util.Log; 20 21 import com.android.bedstead.nene.exceptions.NeneException; 22 import com.android.queryable.Queryable; 23 import com.android.queryable.queries.BooleanQuery; 24 import com.android.queryable.queries.BooleanQueryHelper; 25 import com.android.queryable.queries.IntegerQuery; 26 import com.android.queryable.queries.IntegerQueryHelper; 27 import com.android.queryable.queries.ListQueryHelper; 28 import com.android.queryable.queries.StringQuery; 29 import com.android.queryable.queries.StringQueryHelper; 30 31 import java.time.Duration; 32 import java.time.Instant; 33 import java.util.HashSet; 34 import java.util.Set; 35 36 /** 37 * {@link Queryable} for querying logged metrics. 38 */ 39 public class MetricQueryBuilder implements Queryable { 40 41 private static final String LOG_TAG = "MetricQueryBuilder"; 42 43 private final EnterpriseMetricsRecorder mRecorder; 44 private boolean hasStartedFetchingResults = false; 45 private int mSkippedNextResults = 0; 46 private int mSkippedPollResults = 0; 47 private Set<EnterpriseMetricInfo> mNonMatchingMetrics = new HashSet<>(); 48 49 private final IntegerQueryHelper<MetricQueryBuilder> mTypeQuery = 50 new IntegerQueryHelper<>(this); 51 private final StringQueryHelper<MetricQueryBuilder> mAdminPackageNameQuery = 52 new StringQueryHelper<>(this); 53 private final BooleanQueryHelper<MetricQueryBuilder> mBooleanQuery = 54 new BooleanQueryHelper<>(this); 55 private final ListQueryHelper<MetricQueryBuilder, String> mStringsQuery = 56 new ListQueryHelper<>(this); 57 private final IntegerQueryHelper<MetricQueryBuilder> mIntegerQuery = 58 new IntegerQueryHelper<>(this); 59 MetricQueryBuilder(EnterpriseMetricsRecorder recorder)60 MetricQueryBuilder(EnterpriseMetricsRecorder recorder) { 61 mRecorder = recorder; 62 } 63 64 /** Query for {@link EnterpriseMetricInfo#type()}. */ whereType()65 public IntegerQuery<MetricQueryBuilder> whereType() { 66 if (hasStartedFetchingResults) { 67 throw new IllegalStateException("Cannot modify query after fetching results"); 68 } 69 return mTypeQuery; 70 } 71 72 /** Query for {@link EnterpriseMetricInfo#adminPackageName()}. */ whereAdminPackageName()73 public StringQuery<MetricQueryBuilder> whereAdminPackageName() { 74 if (hasStartedFetchingResults) { 75 throw new IllegalStateException("Cannot modify query after fetching results"); 76 } 77 return mAdminPackageNameQuery; 78 } 79 80 /** Query for {@link EnterpriseMetricInfo#Boolean()}. */ whereBoolean()81 public BooleanQuery<MetricQueryBuilder> whereBoolean() { 82 if (hasStartedFetchingResults) { 83 throw new IllegalStateException("Cannot modify query after fetching results"); 84 } 85 return mBooleanQuery; 86 } 87 88 /** Query for {@link EnterpriseMetricInfo#integer()}. */ whereInteger()89 public IntegerQuery<MetricQueryBuilder> whereInteger() { 90 if (hasStartedFetchingResults) { 91 throw new IllegalStateException("Cannot modify query after fetching results"); 92 } 93 return mIntegerQuery; 94 } 95 whereStrings()96 public ListQueryHelper<MetricQueryBuilder, String> whereStrings() { 97 if (hasStartedFetchingResults) { 98 throw new IllegalStateException("Cannot modify query after fetching results"); 99 } 100 return mStringsQuery; 101 } 102 get()103 public EnterpriseMetricInfo get() { 104 return get(/* skipResults= */ 0); 105 } 106 get(int skipResults)107 private EnterpriseMetricInfo get(int skipResults) { 108 hasStartedFetchingResults = true; 109 for (EnterpriseMetricInfo m : mRecorder.fetchLatestData()) { 110 if (matches(m)) { 111 skipResults -= 1; 112 if (skipResults < 0) { 113 return m; 114 } 115 } else { 116 Log.d(LOG_TAG, "Found non-matching metric " + m); 117 mNonMatchingMetrics.add(m); 118 } 119 } 120 121 return null; 122 } 123 next()124 public EnterpriseMetricInfo next() { 125 hasStartedFetchingResults = true; 126 127 EnterpriseMetricInfo nextResult = get(mSkippedNextResults); 128 if (nextResult != null) { 129 mSkippedNextResults++; 130 } 131 132 return nextResult; 133 } 134 poll()135 public EnterpriseMetricInfo poll() { 136 return poll(Duration.ofSeconds(30)); 137 } 138 poll(Duration timeout)139 public EnterpriseMetricInfo poll(Duration timeout) { 140 hasStartedFetchingResults = true; 141 Instant endTime = Instant.now().plus(timeout); 142 143 while (Instant.now().isBefore(endTime)) { 144 EnterpriseMetricInfo nextResult = get(mSkippedPollResults); 145 if (nextResult != null) { 146 mSkippedPollResults++; 147 return nextResult; 148 } 149 150 try { 151 Thread.sleep(500); 152 } catch (InterruptedException e) { 153 throw new NeneException("Interrupted while polling", e); 154 } 155 } 156 157 return null; 158 } 159 160 /** 161 * Get metrics which were received but didn't match the query. 162 */ nonMatchingMetrics()163 public Set<EnterpriseMetricInfo> nonMatchingMetrics() { 164 return mNonMatchingMetrics; 165 } 166 167 @Override isEmptyQuery()168 public boolean isEmptyQuery() { 169 return Queryable.isEmptyQuery(mAdminPackageNameQuery) 170 && Queryable.isEmptyQuery(mTypeQuery) 171 && Queryable.isEmptyQuery(mBooleanQuery) 172 && Queryable.isEmptyQuery(mStringsQuery) 173 && Queryable.isEmptyQuery(mIntegerQuery); 174 } 175 matches(EnterpriseMetricInfo metric)176 private boolean matches(EnterpriseMetricInfo metric) { 177 return mAdminPackageNameQuery.matches(metric.adminPackageName()) 178 && mTypeQuery.matches(metric.type()) 179 && mBooleanQuery.matches(metric.Boolean()) 180 && mStringsQuery.matches(metric.strings()) 181 && mIntegerQuery.matches(metric.integer()); 182 } 183 184 @Override describeQuery(String fieldName)185 public String describeQuery(String fieldName) { 186 return "{" + Queryable.joinQueryStrings( 187 mAdminPackageNameQuery.describeQuery("adminPackageName"), 188 mTypeQuery.describeQuery("type"), 189 mBooleanQuery.describeQuery("boolean"), 190 mStringsQuery.describeQuery("strings"), 191 mIntegerQuery.describeQuery("integer") 192 ) + "}"; 193 } 194 195 @Override toString()196 public String toString() { 197 return describeQuery(""); 198 } 199 } 200