blob: 3344bf6d93a4631f66c5ceec15ab15a11bea82c7 [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.impala.service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
import org.apache.impala.thrift.TCounter;
import org.apache.impala.thrift.TRuntimeProfileNode;
import org.apache.impala.thrift.TUnit;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.errorprone.annotations.concurrent.GuardedBy;
/**
* Wrapper class for creating a runtime profile within the frontend.
*
* In order to avoid plumbing an object through all code that might want to emit counters
* into the profile, this class provides some support for storing a current profile in
* a thread-local variable.
*
* This class is thread-safe.
*/
@ThreadSafe
public class FrontendProfile {
private static final String ROOT_COUNTER_NAME = "";
private static ThreadLocal<FrontendProfile> THREAD_LOCAL =
new ThreadLocal<>();
@GuardedBy("this")
private TRuntimeProfileNode profile_;
/**
* Name-based access to the counters in the profile_.counters List<TCounter>.
*/
@GuardedBy("this")
private final Map<String, TCounter> countersByName_ = new HashMap<>();
FrontendProfile() {
profile_ = new TRuntimeProfileNode("Frontend",
/*num_children=*/ 0,
/*counters=*/new ArrayList<>(),
/*metadata=*/-1L, // TODO(todd) what is this used for? why is it required?
/*indent=*/false,
/*info_strings=*/new HashMap<>(),
/*info_strings_display_order*/new ArrayList<>(),
/*child_counters_map=*/ImmutableMap.of(ROOT_COUNTER_NAME, new HashSet<>()));
}
/**
* Create a new profile, setting it as the current thread-local profile for the
* length of the current scope. This is meant to be used in a try-with-resources
* statement. Supports at most one scope per thread. No nested scopes are currently
* allowed.
*/
public static Scope createNewWithScope() {
return new Scope(new FrontendProfile());
}
/**
* Get the profile attached to the current thread, throw IllegalStateException if there
* is none.
*/
@Nonnull
public static FrontendProfile getCurrent() {
FrontendProfile prof = THREAD_LOCAL.get();
Preconditions.checkState(prof != null, "no profile in scope");
return prof;
}
/**
* Get the profile attached to the current thread, or null if there is no current
* profile.
*/
@Nullable
public static FrontendProfile getCurrentOrNull() {
return THREAD_LOCAL.get();
}
/**
* Return the profile in Thrift format. This may be called only once, and after it is
* called, no further methods may be used on this PlannerProfile object. Any attempts
* to do so will result in IllegalStateExceptions.
*/
public synchronized TRuntimeProfileNode emitAsThrift() {
Preconditions.checkState(profile_ != null, "already emitted profile");
TRuntimeProfileNode ret = profile_;
profile_ = null;
return ret;
}
/**
* Add an informational key/value string pair to the profile. These are written out
* as is to the user. Subsequent calls with the same key will overwrite previous ones.
*/
public synchronized void addInfoString(String key, String val) {
Preconditions.checkState(profile_ != null, "already emitted profile");
Preconditions.checkNotNull(key);
Preconditions.checkNotNull(val);
if (profile_.getInfo_strings().put(key, val) == null) {
// If it's a new info string instead of replacing an existing one,
// we need to also include it in the 'ordering' list.
profile_.getInfo_strings_display_order().add(key);
}
}
/**
* Add 'delta' to the counter with the given name and unit. Counters are created
* on-demand.
*/
public synchronized void addToCounter(String name, TUnit unit, long delta) {
Preconditions.checkState(profile_ != null, "already emitted profile");
TCounter counter = countersByName_.get(Preconditions.checkNotNull(name));
if (counter == null) {
// Need to create the counter.
counter = new TCounter(name, unit, 0);
countersByName_.put(name, counter);
profile_.counters.add(counter);
// Currently we don't support hierarchical counters in the frontend.
profile_.child_counters_map.get(ROOT_COUNTER_NAME).add(name);
}
counter.value += delta;
}
public static class Scope implements AutoCloseable {
private final FrontendProfile oldThreadLocalValue_;
private Scope(FrontendProfile profile) {
oldThreadLocalValue_ = THREAD_LOCAL.get();
// TODO: remove when allowing nested scopes.
Preconditions.checkState(oldThreadLocalValue_ == null);
THREAD_LOCAL.set(profile);
}
@Override
public void close() {
THREAD_LOCAL.set(oldThreadLocalValue_);
}
}
}