/* * Copyright 2015-2017 the original author or authors. * * Licensed 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.glowroot.central.repo; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.ListIterator; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import javax.annotation.Nullable; import com.datastax.driver.core.BoundStatement; import com.datastax.driver.core.PreparedStatement; import com.datastax.driver.core.ResultSet; import com.datastax.driver.core.ResultSetFuture; import com.datastax.driver.core.Row; import com.datastax.driver.core.Session; import com.google.common.base.Charsets; import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.common.hash.Hashing; import com.google.common.primitives.Ints; import com.google.protobuf.ByteString; import com.google.protobuf.InvalidProtocolBufferException; import org.immutables.value.Value; import org.glowroot.central.util.Messages; import org.glowroot.central.util.MoreFutures; import org.glowroot.central.util.Sessions; import org.glowroot.common.config.StorageConfig; import org.glowroot.common.live.ImmutableEntries; import org.glowroot.common.live.ImmutableTracePoint; import org.glowroot.common.live.LiveTraceRepository.Entries; import org.glowroot.common.live.LiveTraceRepository.Existence; import org.glowroot.common.live.LiveTraceRepository.TracePoint; import org.glowroot.common.live.LiveTraceRepository.TracePointFilter; import org.glowroot.common.model.Result; import org.glowroot.common.repo.ConfigRepository; import org.glowroot.common.repo.ImmutableErrorMessageCount; import org.glowroot.common.repo.ImmutableErrorMessagePoint; import org.glowroot.common.repo.ImmutableErrorMessageResult; import org.glowroot.common.repo.ImmutableHeaderPlus; import org.glowroot.common.repo.TraceRepository; import org.glowroot.common.repo.Utils; import org.glowroot.common.util.Clock; import org.glowroot.common.util.Styles; import org.glowroot.wire.api.model.ProfileOuterClass.Profile; import org.glowroot.wire.api.model.Proto; import org.glowroot.wire.api.model.Proto.StackTraceElement; import org.glowroot.wire.api.model.TraceOuterClass.Trace; import static com.google.common.base.Preconditions.checkNotNull; import static java.util.concurrent.TimeUnit.HOURS; public class TraceDao implements TraceRepository { private final Session session; private final AgentDao agentDao; private final TransactionTypeDao transactionTypeDao; private final FullQueryTextDao fullQueryTextDao; private final TraceAttributeNameDao traceAttributeNameDao; private final ConfigRepository configRepository; private final Clock clock; private final PreparedStatement insertCheck; private final PreparedStatement insertOverallSlowPoint; private final PreparedStatement insertTransactionSlowPoint; private final PreparedStatement insertOverallSlowCount; private final PreparedStatement insertTransactionSlowCount; private final PreparedStatement insertOverallErrorPoint; private final PreparedStatement insertTransactionErrorPoint; private final PreparedStatement insertOverallErrorCount; private final PreparedStatement insertTransactionErrorCount; private final PreparedStatement insertOverallErrorMessage; private final PreparedStatement insertTransactionErrorMessage; private final PreparedStatement insertHeader; private final PreparedStatement insertEntry; private final PreparedStatement insertSharedQueryText; private final PreparedStatement insertMainThreadProfile; private final PreparedStatement insertAuxThreadProfile; private final PreparedStatement readCheck; private final PreparedStatement readOverallSlowPoint; private final PreparedStatement readTransactionSlowPoint; private final PreparedStatement readOverallErrorPoint; private final PreparedStatement readTransactionErrorPoint; private final PreparedStatement readOverallErrorMessage; private final PreparedStatement readTransactionErrorMessage; private final PreparedStatement readHeader; private final PreparedStatement readEntries; private final PreparedStatement readSharedQueryTexts; private final PreparedStatement readMainThreadProfile; private final PreparedStatement readAuxThreadProfile; private final PreparedStatement deletePartialOverallSlowPoint; private final PreparedStatement deletePartialTransactionSlowPoint; private final PreparedStatement deletePartialOverallSlowCount; private final PreparedStatement deletePartialTransactionSlowCount; public TraceDao(Session session, AgentDao agentDao, TransactionTypeDao transactionTypeDao, FullQueryTextDao fullQueryTextDao, TraceAttributeNameDao traceAttributeNameDao, ConfigRepository configRepository, Clock clock) throws Exception { this.session = session; this.agentDao = agentDao; this.transactionTypeDao = transactionTypeDao; this.fullQueryTextDao = fullQueryTextDao; this.traceAttributeNameDao = traceAttributeNameDao; this.configRepository = configRepository; this.clock = clock; int expirationHours = configRepository.getStorageConfig().traceExpirationHours(); Sessions.createTableWithTWCS(session, "create table if not exists trace_check" + " (agent_rollup varchar, agent_id varchar, trace_id varchar, primary key" + " ((agent_rollup, agent_id), trace_id))", expirationHours); Sessions.createTableWithTWCS(session, "create table if not exists trace_tt_slow_point" + " (agent_rollup varchar, transaction_type varchar, capture_time timestamp," + " agent_id varchar, trace_id varchar, duration_nanos bigint, partial boolean," + " error boolean, headline varchar, user varchar, attributes blob, primary key" + " ((agent_rollup, transaction_type), capture_time, agent_id, trace_id))", expirationHours); Sessions.createTableWithTWCS(session, "create table if not exists trace_tn_slow_point" + " (agent_rollup varchar, transaction_type varchar, transaction_name varchar," + " capture_time timestamp, agent_id varchar, trace_id varchar," + " duration_nanos bigint, partial boolean, error boolean, headline varchar," + " user varchar, attributes blob, primary key ((agent_rollup, transaction_type," + " transaction_name), capture_time, agent_id, trace_id))", expirationHours); Sessions.createTableWithTWCS(session, "create table if not exists trace_tt_error_point" + " (agent_rollup varchar, transaction_type varchar, capture_time timestamp," + " agent_id varchar, trace_id varchar, duration_nanos bigint, partial boolean," + " error_message varchar, headline varchar, user varchar, attributes blob," + " primary key ((agent_rollup, transaction_type), capture_time, agent_id," + " trace_id))", expirationHours); Sessions.createTableWithTWCS(session, "create table if not exists trace_tn_error_point" + " (agent_rollup varchar, transaction_type varchar, transaction_name varchar," + " capture_time timestamp, agent_id varchar, trace_id varchar," + " duration_nanos bigint, partial boolean, error_message varchar," + " headline varchar, user varchar, attributes blob, primary key ((agent_rollup," + " transaction_type, transaction_name), capture_time, agent_id, trace_id))", expirationHours); Sessions.createTableWithTWCS(session, "create table if not exists trace_tt_error_message" + " (agent_rollup varchar, transaction_type varchar, capture_time timestamp," + " agent_id varchar, trace_id varchar, error_message varchar, primary key" + " ((agent_rollup, transaction_type), capture_time, agent_id, trace_id))", expirationHours); Sessions.createTableWithTWCS(session, "create table if not exists trace_tn_error_message" + " (agent_rollup varchar, transaction_type varchar, transaction_name varchar," + " capture_time timestamp, agent_id varchar, trace_id varchar," + " error_message varchar, primary key ((agent_rollup, transaction_type," + " transaction_name), capture_time, agent_id, trace_id))", expirationHours); Sessions.createTableWithTWCS(session, "create table if not exists trace_header" + " (agent_id varchar, trace_id varchar, header blob, primary key (agent_id," + " trace_id))", expirationHours); // index_ is just to provide uniqueness Sessions.createTableWithTWCS(session, "create table if not exists trace_entry" + " (agent_id varchar, trace_id varchar, index_ int, depth int," + " start_offset_nanos bigint, duration_nanos bigint, active boolean," + " message varchar, shared_query_text_index int, query_message_prefix varchar," + " query_message_suffix varchar, detail blob, location_stack_trace blob," + " error blob, primary key (agent_id, trace_id, index_))", expirationHours); // index_ is just to provide uniqueness Sessions.createTableWithTWCS(session, "create table if not exists trace_shared_query_text" + " (agent_id varchar, trace_id varchar, index_ int, truncated_text varchar," + " truncated_end_text varchar, full_text_sha1 varchar, primary key (agent_id," + " trace_id, index_))", expirationHours); Sessions.createTableWithTWCS(session, "create table if not exists trace_main_thread_profile" + " (agent_id varchar, trace_id varchar, profile blob, primary key (agent_id," + " trace_id))", expirationHours); Sessions.createTableWithTWCS(session, "create table if not exists trace_aux_thread_profile" + " (agent_id varchar, trace_id varchar, profile blob, primary key (agent_id," + " trace_id))", expirationHours); // agent_rollup/capture_time is not necessarily unique // using a counter would be nice since only need sum over capture_time range // but counter has no TTL, see https://issues.apache.org/jira/browse/CASSANDRA-2103 // so adding trace_id to provide uniqueness Sessions.createTableWithTWCS(session, "create table if not exists trace_tt_slow_count" + " (agent_rollup varchar, transaction_type varchar, capture_time timestamp," + " agent_id varchar, trace_id varchar, primary key ((agent_rollup," + " transaction_type), capture_time, agent_id, trace_id))", expirationHours); Sessions.createTableWithTWCS(session, "create table if not exists trace_tn_slow_count" + " (agent_rollup varchar, transaction_type varchar, transaction_name varchar," + " capture_time timestamp, agent_id varchar, trace_id varchar, primary key" + " ((agent_rollup, transaction_type, transaction_name), capture_time, agent_id," + " trace_id))", expirationHours); Sessions.createTableWithTWCS(session, "create table if not exists trace_tt_error_count" + " (agent_rollup varchar, transaction_type varchar, capture_time timestamp," + " agent_id varchar, trace_id varchar, primary key ((agent_rollup," + " transaction_type), capture_time, agent_id, trace_id))", expirationHours); Sessions.createTableWithTWCS(session, "create table if not exists trace_tn_error_count" + " (agent_rollup varchar, transaction_type varchar, transaction_name varchar," + " capture_time timestamp, agent_id varchar, trace_id varchar, primary key" + " ((agent_rollup, transaction_type, transaction_name), capture_time, agent_id," + " trace_id))", expirationHours); insertCheck = session.prepare("insert into trace_check (agent_rollup, agent_id, trace_id)" + " values (?, ?, ?) using ttl ?"); insertOverallSlowPoint = session.prepare("insert into trace_tt_slow_point (agent_rollup," + " transaction_type, capture_time, agent_id, trace_id, duration_nanos, partial," + " error, headline, user, attributes) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" + " using ttl ?"); insertTransactionSlowPoint = session.prepare("insert into trace_tn_slow_point" + " (agent_rollup, transaction_type, transaction_name, capture_time, agent_id," + " trace_id, duration_nanos, partial, error, headline, user, attributes) values" + " (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) using ttl ?"); insertOverallSlowCount = session.prepare("insert into trace_tt_slow_count (agent_rollup," + " transaction_type, capture_time, agent_id, trace_id) values (?, ?, ?, ?, ?)" + " using ttl ?"); insertTransactionSlowCount = session.prepare("insert into trace_tn_slow_count" + " (agent_rollup, transaction_type, transaction_name, capture_time, agent_id," + " trace_id) values (?, ?, ?, ?, ?, ?) using ttl ?"); insertOverallErrorPoint = session.prepare("insert into trace_tt_error_point (agent_rollup," + " transaction_type, capture_time, agent_id, trace_id, duration_nanos, partial," + " error_message, headline, user, attributes) values" + " (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) using ttl ?"); insertTransactionErrorPoint = session.prepare("insert into trace_tn_error_point" + " (agent_rollup, transaction_type, transaction_name, capture_time, agent_id," + " trace_id, duration_nanos, partial, error_message, headline, user, attributes)" + " values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) using ttl ?"); insertOverallErrorCount = session.prepare("insert into trace_tt_error_count (agent_rollup," + " transaction_type, capture_time, agent_id, trace_id) values (?, ?, ?, ?, ?)" + " using ttl ?"); insertTransactionErrorCount = session.prepare("insert into trace_tn_error_count" + " (agent_rollup, transaction_type, transaction_name, capture_time, agent_id," + " trace_id) values (?, ?, ?, ?, ?, ?) using ttl ?"); insertOverallErrorMessage = session.prepare("insert into trace_tt_error_message" + " (agent_rollup, transaction_type, capture_time, agent_id, trace_id," + " error_message) values (?, ?, ?, ?, ?, ?) using ttl ?"); insertTransactionErrorMessage = session.prepare("insert into trace_tn_error_message" + " (agent_rollup, transaction_type, transaction_name, capture_time, agent_id," + " trace_id, error_message) values (?, ?, ?, ?, ?, ?, ?) using ttl ?"); insertHeader = session.prepare("insert into trace_header (agent_id, trace_id, header)" + " values (?, ?, ?) using ttl ?"); insertEntry = session.prepare("insert into trace_entry (agent_id, trace_id, index_, depth," + " start_offset_nanos, duration_nanos, active, message, shared_query_text_index," + " query_message_prefix, query_message_suffix, detail, location_stack_trace," + " error) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) using ttl ?"); insertSharedQueryText = session.prepare("insert into trace_shared_query_text (agent_id," + " trace_id, index_, truncated_text, truncated_end_text, full_text_sha1) values" + " (?, ?, ?, ?, ?, ?) using ttl ?"); insertMainThreadProfile = session.prepare("insert into trace_main_thread_profile" + " (agent_id, trace_id, profile) values (?, ?, ?) using ttl ?"); insertAuxThreadProfile = session.prepare("insert into trace_aux_thread_profile" + " (agent_id, trace_id, profile) values (?, ?, ?) using ttl ?"); readCheck = session.prepare("select trace_id from trace_check where agent_rollup = ?" + " and agent_id = ? and trace_id = ?"); readOverallSlowPoint = session.prepare("select agent_id, trace_id, capture_time," + " duration_nanos, partial, error, headline, user, attributes" + " from trace_tt_slow_point where agent_rollup = ? and transaction_type = ?" + " and capture_time > ? and capture_time <= ?"); readTransactionSlowPoint = session.prepare("select agent_id, trace_id, capture_time," + " duration_nanos, partial, error, headline, user, attributes" + " from trace_tn_slow_point where agent_rollup = ? and transaction_type = ?" + " and transaction_name = ? and capture_time > ? and capture_time <= ?"); readOverallErrorPoint = session.prepare("select agent_id, trace_id, capture_time," + " duration_nanos, partial, error_message, headline, user, attributes" + " from trace_tt_error_point where agent_rollup = ? and transaction_type = ?" + " and capture_time > ? and capture_time <= ?"); readTransactionErrorPoint = session.prepare("select agent_id, trace_id, capture_time," + " duration_nanos, partial, error_message, headline, user, attributes" + " from trace_tn_error_point where agent_rollup = ? and transaction_type = ?" + " and transaction_name = ? and capture_time > ? and capture_time <= ?"); readOverallErrorMessage = session.prepare("select capture_time, error_message" + " from trace_tt_error_message where agent_rollup = ? and transaction_type = ?" + " and capture_time > ? and capture_time <= ?"); readTransactionErrorMessage = session.prepare("select capture_time, error_message" + " from trace_tn_error_message where agent_rollup = ? and transaction_type = ?" + " and transaction_name = ? and capture_time > ? and capture_time <= ?"); readHeader = session .prepare("select header from trace_header where agent_id = ? and trace_id = ?"); readEntries = session.prepare("select depth, start_offset_nanos, duration_nanos," + " active, message, shared_query_text_index, query_message_prefix," + " query_message_suffix, detail, location_stack_trace, error from trace_entry" + " where agent_id = ? and trace_id = ?"); readSharedQueryTexts = session.prepare("select truncated_text, truncated_end_text," + " full_text_sha1 from trace_shared_query_text where agent_id = ?" + " and trace_id = ?"); readMainThreadProfile = session.prepare("select profile from trace_main_thread_profile" + " where agent_id = ? and trace_id = ?"); readAuxThreadProfile = session.prepare("select profile from trace_aux_thread_profile" + " where agent_id = ? and trace_id = ?"); deletePartialOverallSlowPoint = session.prepare("delete from trace_tt_slow_point" + " where agent_rollup = ? and transaction_type = ? and capture_time = ?" + " and agent_id = ? and trace_id = ?"); deletePartialTransactionSlowPoint = session.prepare("delete from trace_tn_slow_point" + " where agent_rollup = ? and transaction_type = ? and transaction_name = ?" + " and capture_time = ? and agent_id = ? and trace_id = ?"); deletePartialOverallSlowCount = session.prepare("delete from trace_tt_slow_count" + " where agent_rollup = ? and transaction_type = ? and capture_time = ?" + " and agent_id = ? and trace_id = ?"); deletePartialTransactionSlowCount = session.prepare("delete from trace_tn_slow_count" + " where agent_rollup = ? and transaction_type = ? and transaction_name = ?" + " and capture_time = ? and agent_id = ? and trace_id = ?"); } public void store(String agentId, Trace trace) throws Exception { String traceId = trace.getId(); Trace.Header priorHeader = trace.getUpdate() ? readHeader(agentId, traceId) : null; Trace.Header header = trace.getHeader(); List<String> agentRollupIds = agentDao.readAgentRollupIds(agentId); List<ResultSetFuture> futures = Lists.newArrayList(); List<Trace.SharedQueryText> sharedQueryTexts = Lists.newArrayList(); for (Trace.SharedQueryText sharedQueryText : trace.getSharedQueryTextList()) { String fullTextSha1 = sharedQueryText.getFullTextSha1(); if (fullTextSha1.isEmpty()) { String fullText = sharedQueryText.getFullText(); if (fullText.length() > 2 * StorageConfig.TRACE_QUERY_TEXT_TRUNCATE) { fullTextSha1 = Hashing.sha1().hashString(fullText, Charsets.UTF_8).toString(); futures.addAll(fullQueryTextDao.store(agentId, fullTextSha1, fullText)); for (int i = 1; i < agentRollupIds.size(); i++) { futures.addAll( fullQueryTextDao.updateCheckTTL(agentRollupIds.get(i), fullTextSha1)); } sharedQueryTexts.add(Trace.SharedQueryText.newBuilder() .setTruncatedText( fullText.substring(0, StorageConfig.TRACE_QUERY_TEXT_TRUNCATE)) .setTruncatedEndText(fullText.substring( fullText.length() - StorageConfig.TRACE_QUERY_TEXT_TRUNCATE, fullText.length())) .setFullTextSha1(fullTextSha1) .build()); } else { sharedQueryTexts.add(sharedQueryText); } } else { futures.addAll(fullQueryTextDao.updateTTL(agentId, fullTextSha1)); for (int i = 1; i < agentRollupIds.size(); i++) { futures.addAll( fullQueryTextDao.updateCheckTTL(agentRollupIds.get(i), fullTextSha1)); } sharedQueryTexts.add(sharedQueryText); } } // wait for success before proceeding in order to ensure cannot end up with orphaned // fullTextSha1 MoreFutures.waitForAll(futures); futures.clear(); int adjustedTTL = AggregateDao.getAdjustedTTL(getTTL(), header.getCaptureTime(), clock); for (String agentRollupId : agentRollupIds) { if (!agentRollupId.equals(agentId)) { BoundStatement boundStatement = insertCheck.bind(); int i = 0; boundStatement.setString(i++, agentRollupId); boundStatement.setString(i++, agentId); boundStatement.setString(i++, traceId); boundStatement.setInt(i++, adjustedTTL); futures.add(session.executeAsync(boundStatement)); } if (header.getSlow()) { BoundStatement boundStatement = insertOverallSlowPoint.bind(); bindSlowPoint(boundStatement, agentRollupId, agentId, traceId, header, adjustedTTL, true); futures.add(session.executeAsync(boundStatement)); boundStatement = insertTransactionSlowPoint.bind(); bindSlowPoint(boundStatement, agentRollupId, agentId, traceId, header, adjustedTTL, false); futures.add(session.executeAsync(boundStatement)); boundStatement = insertOverallSlowCount.bind(); bindCount(boundStatement, agentRollupId, agentId, traceId, header, adjustedTTL, true); futures.add(session.executeAsync(boundStatement)); boundStatement = insertTransactionSlowCount.bind(); bindCount(boundStatement, agentRollupId, agentId, traceId, header, adjustedTTL, false); futures.add(session.executeAsync(boundStatement)); if (priorHeader != null) { boundStatement = deletePartialOverallSlowPoint.bind(); bind(boundStatement, agentRollupId, agentId, traceId, priorHeader, true); futures.add(session.executeAsync(boundStatement)); boundStatement = deletePartialTransactionSlowPoint.bind(); bind(boundStatement, agentRollupId, agentId, traceId, priorHeader, false); futures.add(session.executeAsync(boundStatement)); boundStatement = deletePartialOverallSlowCount.bind(); bind(boundStatement, agentRollupId, agentId, traceId, priorHeader, true); futures.add(session.executeAsync(boundStatement)); boundStatement = deletePartialTransactionSlowCount.bind(); bind(boundStatement, agentRollupId, agentId, traceId, priorHeader, false); futures.add(session.executeAsync(boundStatement)); } } // seems unnecessary to insert error info for partial traces // and this avoids having to clean up partial trace data when trace is complete if (header.hasError() && !header.getPartial()) { BoundStatement boundStatement = insertOverallErrorMessage.bind(); bindErrorMessage(boundStatement, agentRollupId, agentId, traceId, header, adjustedTTL, true); futures.add(session.executeAsync(boundStatement)); boundStatement = insertTransactionErrorMessage.bind(); bindErrorMessage(boundStatement, agentRollupId, agentId, traceId, header, adjustedTTL, false); futures.add(session.executeAsync(boundStatement)); boundStatement = insertOverallErrorPoint.bind(); bindErrorPoint(boundStatement, agentRollupId, agentId, traceId, header, adjustedTTL, true); futures.add(session.executeAsync(boundStatement)); boundStatement = insertTransactionErrorPoint.bind(); bindErrorPoint(boundStatement, agentRollupId, agentId, traceId, header, adjustedTTL, false); futures.add(session.executeAsync(boundStatement)); boundStatement = insertOverallErrorCount.bind(); bindCount(boundStatement, agentRollupId, agentId, traceId, header, adjustedTTL, true); futures.add(session.executeAsync(boundStatement)); boundStatement = insertTransactionErrorCount.bind(); bindCount(boundStatement, agentRollupId, agentId, traceId, header, adjustedTTL, false); futures.add(session.executeAsync(boundStatement)); } for (Trace.Attribute attributeName : header.getAttributeList()) { traceAttributeNameDao.store(agentRollupId, header.getTransactionType(), attributeName.getName(), futures); } } BoundStatement boundStatement = insertHeader.bind(); int i = 0; boundStatement.setString(i++, agentId); boundStatement.setString(i++, traceId); boundStatement.setBytes(i++, ByteBuffer.wrap(header.toByteArray())); boundStatement.setInt(i++, adjustedTTL); futures.add(session.executeAsync(boundStatement)); int index = 0; for (Trace.Entry entry : trace.getEntryList()) { boundStatement = insertEntry.bind(); i = 0; boundStatement.setString(i++, agentId); boundStatement.setString(i++, traceId); boundStatement.setInt(i++, index++); boundStatement.setInt(i++, entry.getDepth()); boundStatement.setLong(i++, entry.getStartOffsetNanos()); boundStatement.setLong(i++, entry.getDurationNanos()); boundStatement.setBool(i++, entry.getActive()); if (entry.hasQueryEntryMessage()) { boundStatement.setToNull(i++); boundStatement.setInt(i++, entry.getQueryEntryMessage().getSharedQueryTextIndex()); boundStatement.setString(i++, Strings.emptyToNull(entry.getQueryEntryMessage().getPrefix())); boundStatement.setString(i++, Strings.emptyToNull(entry.getQueryEntryMessage().getSuffix())); } else { // message is empty for trace entries added using addErrorEntry() boundStatement.setString(i++, Strings.emptyToNull(entry.getMessage())); boundStatement.setToNull(i++); boundStatement.setToNull(i++); boundStatement.setToNull(i++); } List<Trace.DetailEntry> detailEntries = entry.getDetailEntryList(); if (detailEntries.isEmpty()) { boundStatement.setToNull(i++); } else { boundStatement.setBytes(i++, Messages.toByteBuffer(detailEntries)); } List<StackTraceElement> location = entry.getLocationStackTraceElementList(); if (location.isEmpty()) { boundStatement.setToNull(i++); } else { boundStatement.setBytes(i++, Messages.toByteBuffer(location)); } if (entry.hasError()) { boundStatement.setBytes(i++, ByteBuffer.wrap(entry.getError().toByteArray())); } else { boundStatement.setToNull(i++); } boundStatement.setInt(i++, adjustedTTL); futures.add(session.executeAsync(boundStatement)); } index = 0; for (Trace.SharedQueryText sharedQueryText : sharedQueryTexts) { boundStatement = insertSharedQueryText.bind(); i = 0; boundStatement.setString(i++, agentId); boundStatement.setString(i++, traceId); boundStatement.setInt(i++, index++); String fullText = sharedQueryText.getFullText(); if (fullText.isEmpty()) { boundStatement.setString(i++, sharedQueryText.getTruncatedText()); boundStatement.setString(i++, sharedQueryText.getTruncatedEndText()); boundStatement.setString(i++, sharedQueryText.getFullTextSha1()); } else { boundStatement.setString(i++, fullText); boundStatement.setToNull(i++); boundStatement.setToNull(i++); } boundStatement.setInt(i++, adjustedTTL); futures.add(session.executeAsync(boundStatement)); } if (trace.hasMainThreadProfile()) { boundStatement = insertMainThreadProfile.bind(); bindThreadProfile(boundStatement, agentId, traceId, trace.getMainThreadProfile(), adjustedTTL); futures.add(session.executeAsync(boundStatement)); } if (trace.hasAuxThreadProfile()) { boundStatement = insertAuxThreadProfile.bind(); bindThreadProfile(boundStatement, agentId, traceId, trace.getAuxThreadProfile(), adjustedTTL); futures.add(session.executeAsync(boundStatement)); } futures.addAll(transactionTypeDao.store(agentRollupIds, header.getTransactionType())); MoreFutures.waitForAll(futures); } @Override public Result<TracePoint> readSlowPoints(String agentRollupId, TraceQuery query, TracePointFilter filter, int limit) throws IOException { BoundStatement boundStatement; String transactionName = query.transactionName(); if (transactionName == null) { boundStatement = readOverallSlowPoint.bind(); bindTraceQuery(boundStatement, agentRollupId, query, true); } else { boundStatement = readTransactionSlowPoint.bind(); bindTraceQuery(boundStatement, agentRollupId, query, false); } ResultSet results = session.execute(boundStatement); return processPoints(results, filter, limit, false); } @Override public Result<TracePoint> readErrorPoints(String agentRollupId, TraceQuery query, TracePointFilter filter, int limit) throws IOException { BoundStatement boundStatement; String transactionName = query.transactionName(); if (transactionName == null) { boundStatement = readOverallErrorPoint.bind(); bindTraceQuery(boundStatement, agentRollupId, query, true); } else { boundStatement = readTransactionErrorPoint.bind(); bindTraceQuery(boundStatement, agentRollupId, query, false); } ResultSet results = session.execute(boundStatement); return processPoints(results, filter, limit, true); } @Override public long readSlowCount(String agentRollupId, TraceQuery query) { String transactionName = query.transactionName(); if (transactionName == null) { ResultSet results = session.execute( "select count(*) from trace_tt_slow_count where agent_rollup = ?" + " and transaction_type = ? and capture_time > ?" + " and capture_time <= ?", agentRollupId, query.transactionType(), query.from(), query.to()); return results.one().getLong(0); } else { ResultSet results = session.execute( "select count(*) from trace_tn_slow_count where agent_rollup = ?" + " and transaction_type = ? and transaction_name = ?" + " and capture_time > ? and capture_time <= ?", agentRollupId, query.transactionType(), transactionName, query.from(), query.to()); return results.one().getLong(0); } } @Override public long readErrorCount(String agentRollupId, TraceQuery query) { String transactionName = query.transactionName(); if (transactionName == null) { ResultSet results = session.execute( "select count(*) from trace_tt_error_count where agent_rollup = ?" + " and transaction_type = ? and capture_time > ?" + " and capture_time <= ?", agentRollupId, query.transactionType(), query.from(), query.to()); return results.one().getLong(0); } else { ResultSet results = session.execute( "select count(*) from trace_tn_error_count where agent_rollup = ?" + " and transaction_type = ? and transaction_name = ?" + " and capture_time > ? and capture_time <= ?", agentRollupId, query.transactionType(), transactionName, query.from(), query.to()); return results.one().getLong(0); } } @Override public ErrorMessageResult readErrorMessages(String agentRollupId, TraceQuery query, ErrorMessageFilter filter, long resolutionMillis, int limit) throws Exception { BoundStatement boundStatement; String transactionName = query.transactionName(); if (transactionName == null) { boundStatement = readOverallErrorMessage.bind(); bindTraceQuery(boundStatement, agentRollupId, query, true); } else { boundStatement = readTransactionErrorMessage.bind(); bindTraceQuery(boundStatement, agentRollupId, query, false); } ResultSet results = session.execute(boundStatement); // rows are already in order by captureTime, so saving sort step by using linked hash map Map<Long, MutableLong> pointCounts = Maps.newLinkedHashMap(); Map<String, MutableLong> messageCounts = Maps.newHashMap(); for (Row row : results) { long captureTime = checkNotNull(row.getTimestamp(0)).getTime(); String errorMessage = checkNotNull(row.getString(1)); if (!matches(filter, errorMessage)) { continue; } long rollupCaptureTime = Utils.getRollupCaptureTime(captureTime, resolutionMillis); pointCounts.computeIfAbsent(rollupCaptureTime, k -> new MutableLong()).increment(); messageCounts.computeIfAbsent(errorMessage, k -> new MutableLong()).increment(); } List<ErrorMessagePoint> points = pointCounts.entrySet().stream() .map(e -> ImmutableErrorMessagePoint.of(e.getKey(), e.getValue().value)) .sorted(Comparator.comparingLong(ErrorMessagePoint::captureTime)) // explicit type on this line is needed for Checker Framework // see https://github.com/typetools/checker-framework/issues/531 .collect(Collectors.<ErrorMessagePoint>toList()); List<ErrorMessageCount> counts = messageCounts.entrySet().stream() .map(e -> ImmutableErrorMessageCount.of(e.getKey(), e.getValue().value)) // points above are already ordered by cassandra, but need to reverse sort counts .sorted(Comparator.comparing(ErrorMessageCount::count).reversed()) // explicit type on this line is needed for Checker Framework // see https://github.com/typetools/checker-framework/issues/531 .collect(Collectors.<ErrorMessageCount>toList()); if (counts.size() <= limit) { return ImmutableErrorMessageResult.builder() .addAllPoints(points) .counts(new Result<>(counts, false)) .build(); } else { return ImmutableErrorMessageResult.builder() .addAllPoints(points) .counts(new Result<>(counts.subList(0, limit), true)) .build(); } } @Override public @Nullable HeaderPlus readHeaderPlus(String agentRollupId, String agentId, String traceId) throws InvalidProtocolBufferException { checkValidAgentIdForRequest(agentRollupId, agentId, traceId); Trace.Header header = readHeader(agentId, traceId); if (header == null) { return null; } Existence entriesExistence = header.getEntryCount() == 0 ? Existence.NO : Existence.YES; Existence profileExistence = header.getMainThreadProfileSampleCount() == 0 && header.getAuxThreadProfileSampleCount() == 0 ? Existence.NO : Existence.YES; return ImmutableHeaderPlus.of(header, entriesExistence, profileExistence); } @Override public Entries readEntries(String agentRollupId, String agentId, String traceId) throws IOException { checkValidAgentIdForRequest(agentRollupId, agentId, traceId); return ImmutableEntries.builder() .addAllEntries(readEntriesInternal(agentId, traceId)) .addAllSharedQueryTexts(readSharedQueryTexts(agentId, traceId)) .build(); } // since this is only used by export, SharedQueryTexts are always returned with fullTrace // (never with truncatedText/truncatedEndText/fullTraceSha1) @Override @Override public Entries readEntriesForExport(String agentRollupId, String agentId, String traceId) throws IOException { checkValidAgentIdForRequest(agentRollupId, agentId, traceId); ImmutableEntries.Builder entries = ImmutableEntries.builder() .addAllEntries(readEntriesInternal(agentId, traceId)); List<Trace.SharedQueryText> sharedQueryTexts = Lists.newArrayList(); for (Trace.SharedQueryText sharedQueryText : readSharedQueryTexts(agentId, traceId)) { String fullTextSha1 = sharedQueryText.getFullTextSha1(); if (fullTextSha1.isEmpty()) { sharedQueryTexts.add(sharedQueryText); } else { String fullText = fullQueryTextDao.getFullText(agentId, fullTextSha1); if (fullText == null) { sharedQueryTexts.add(Trace.SharedQueryText.newBuilder() .setFullText(sharedQueryText.getTruncatedText() + " ... [full query text has expired] ... " + sharedQueryText.getTruncatedEndText()) .build()); } else { sharedQueryTexts.add(Trace.SharedQueryText.newBuilder() .setFullText(fullText) .build()); } } } return entries.addAllSharedQueryTexts(sharedQueryTexts) .build(); } @Override public @Nullable Profile readMainThreadProfile(String agentRollupId, String agentId, String traceId) throws InvalidProtocolBufferException { checkValidAgentIdForRequest(agentRollupId, agentId, traceId); BoundStatement boundStatement = readMainThreadProfile.bind(); boundStatement.setString(0, agentId); boundStatement.setString(1, traceId); ResultSet results = session.execute(boundStatement); Row row = results.one(); if (row == null) { return null; } ByteBuffer bytes = checkNotNull(row.getBytes(0)); return Profile.parseFrom(ByteString.copyFrom(bytes)); } @Override public @Nullable Profile readAuxThreadProfile(String agentRollupId, String agentId, String traceId) throws InvalidProtocolBufferException { checkValidAgentIdForRequest(agentRollupId, agentId, traceId); BoundStatement boundStatement = readAuxThreadProfile.bind(); boundStatement.setString(0, agentId); boundStatement.setString(1, traceId); ResultSet results = session.execute(boundStatement); Row row = results.one(); if (row == null) { return null; } ByteBuffer bytes = checkNotNull(row.getBytes(0)); return Profile.parseFrom(ByteString.copyFrom(bytes)); } private void checkValidAgentIdForRequest(String agentRollupId, String agentId, String traceId) { if (agentId.equals(agentRollupId)) { return; } BoundStatement boundStatement = readCheck.bind(); int i = 0; boundStatement.setString(i++, agentRollupId); boundStatement.setString(i++, agentId); boundStatement.setString(i++, traceId); if (session.execute(boundStatement).isExhausted()) { throw new IllegalArgumentException("Agent " + agentId + " was not a child of rollup " + agentRollupId + " at the time of trace " + traceId); } } private @Nullable Trace.Header readHeader(String agentId, String traceId) throws InvalidProtocolBufferException { BoundStatement boundStatement = readHeader.bind(); boundStatement.setString(0, agentId); boundStatement.setString(1, traceId); ResultSet results = session.execute(boundStatement); Row row = results.one(); if (row == null) { return null; } ByteBuffer bytes = checkNotNull(row.getBytes(0)); return Trace.Header.parseFrom(ByteString.copyFrom(bytes)); } private List<Trace.Entry> readEntriesInternal(String agentId, String traceId) throws IOException { BoundStatement boundStatement = readEntries.bind(); boundStatement.setString(0, agentId); boundStatement.setString(1, traceId); ResultSet results = session.execute(boundStatement); List<Trace.Entry> entries = Lists.newArrayList(); while (!results.isExhausted()) { Row row = results.one(); int i = 0; Trace.Entry.Builder entry = Trace.Entry.newBuilder() .setDepth(row.getInt(i++)) .setStartOffsetNanos(row.getLong(i++)) .setDurationNanos(row.getLong(i++)) .setActive(row.getBool(i++)); if (row.isNull(i + 1)) { // shared_query_text_index // message is null for trace entries added using addErrorEntry() entry.setMessage(Strings.nullToEmpty(row.getString(i++))); i++; // shared_query_text_index i++; // query_message_prefix i++; // query_message_suffix } else { i++; // message Trace.QueryEntryMessage queryEntryMessage = Trace.QueryEntryMessage.newBuilder() .setSharedQueryTextIndex(row.getInt(i++)) .setPrefix(Strings.nullToEmpty(row.getString(i++))) .setSuffix(Strings.nullToEmpty(row.getString(i++))) .build(); entry.setQueryEntryMessage(queryEntryMessage); } ByteBuffer detailBytes = row.getBytes(i++); if (detailBytes != null) { entry.addAllDetailEntry( Messages.parseDelimitedFrom(detailBytes, Trace.DetailEntry.parser())); } ByteBuffer locationBytes = row.getBytes(i++); if (locationBytes != null) { entry.addAllLocationStackTraceElement(Messages.parseDelimitedFrom(locationBytes, Proto.StackTraceElement.parser())); } ByteBuffer errorBytes = row.getBytes(i++); if (errorBytes != null) { entry.setError(Trace.Error.parseFrom(ByteString.copyFrom(errorBytes))); } entries.add(entry.build()); } return entries; } private List<Trace.SharedQueryText> readSharedQueryTexts(String agentId, String traceId) throws IOException { BoundStatement boundStatement = readSharedQueryTexts.bind(); boundStatement.setString(0, agentId); boundStatement.setString(1, traceId); ResultSet results = session.execute(boundStatement); List<Trace.SharedQueryText> sharedQueryTexts = Lists.newArrayList(); while (!results.isExhausted()) { Row row = results.one(); int i = 0; String truncatedText = checkNotNull(row.getString(i++)); String truncatedEndText = row.getString(i++); String fullTextSha1 = row.getString(i++); Trace.SharedQueryText.Builder sharedQueryText = Trace.SharedQueryText.newBuilder(); if (fullTextSha1 == null) { sharedQueryText.setFullText(truncatedText); } else { sharedQueryText.setFullTextSha1(fullTextSha1) .setTruncatedText(truncatedText) .setTruncatedEndText(checkNotNull(truncatedEndText)); } sharedQueryTexts.add(sharedQueryText.build()); } return sharedQueryTexts; } private int getTTL() throws Exception { return Ints.saturatedCast( HOURS.toSeconds(configRepository.getStorageConfig().traceExpirationHours())); } private static void bindSlowPoint(BoundStatement boundStatement, String agentRollupId, String agentId, String traceId, Trace.Header header, int adjustedTTL, boolean overall) throws IOException { int i = bind(boundStatement, agentRollupId, agentId, traceId, header, overall); boundStatement.setLong(i++, header.getDurationNanos()); boundStatement.setBool(i++, header.getPartial()); boundStatement.setBool(i++, header.hasError()); boundStatement.setString(i++, header.getHeadline()); boundStatement.setString(i++, Strings.emptyToNull(header.getUser())); List<Trace.Attribute> attributes = header.getAttributeList(); if (attributes.isEmpty()) { boundStatement.setToNull(i++); } else { boundStatement.setBytes(i++, Messages.toByteBuffer(attributes)); } boundStatement.setInt(i++, adjustedTTL); } private static void bindCount(BoundStatement boundStatement, String agentRollupId, String agentId, String traceId, Trace.Header header, int adjustedTTL, boolean overall) { int i = bind(boundStatement, agentRollupId, agentId, traceId, header, overall); boundStatement.setInt(i++, adjustedTTL); } private static void bindErrorMessage(BoundStatement boundStatement, String agentRollupId, String agentId, String traceId, Trace.Header header, int adjustedTTL, boolean overall) { int i = bind(boundStatement, agentRollupId, agentId, traceId, header, overall); boundStatement.setString(i++, header.getError().getMessage()); boundStatement.setInt(i++, adjustedTTL); } private static void bindErrorPoint(BoundStatement boundStatement, String agentRollupId, String agentId, String traceId, Trace.Header header, int adjustedTTL, boolean overall) throws IOException { int i = bind(boundStatement, agentRollupId, agentId, traceId, header, overall); boundStatement.setLong(i++, header.getDurationNanos()); boundStatement.setBool(i++, header.getPartial()); boundStatement.setString(i++, header.getError().getMessage()); boundStatement.setString(i++, header.getHeadline()); boundStatement.setString(i++, Strings.emptyToNull(header.getUser())); List<Trace.Attribute> attributes = header.getAttributeList(); if (attributes.isEmpty()) { boundStatement.setToNull(i++); } else { boundStatement.setBytes(i++, Messages.toByteBuffer(attributes)); } boundStatement.setInt(i++, adjustedTTL); } private static int bind(BoundStatement boundStatement, String agentRollupId, String agentId, String traceId, Trace.Header header, boolean overall) { int i = 0; boundStatement.setString(i++, agentRollupId); boundStatement.setString(i++, header.getTransactionType()); if (!overall) { boundStatement.setString(i++, header.getTransactionName()); } boundStatement.setTimestamp(i++, new Date(header.getCaptureTime())); boundStatement.setString(i++, agentId); boundStatement.setString(i++, traceId); return i; } private static void bindThreadProfile(BoundStatement boundStatement, String agentId, String traceId, Profile profile, int adjustedTTL) { int i = 0; boundStatement.setString(i++, agentId); boundStatement.setString(i++, traceId); boundStatement.setBytes(i++, ByteBuffer.wrap(profile.toByteArray())); boundStatement.setInt(i++, adjustedTTL); } private static void bindTraceQuery(BoundStatement boundStatement, String agentRollupId, TraceQuery query, boolean overall) { int i = 0; boundStatement.setString(i++, agentRollupId); boundStatement.setString(i++, query.transactionType()); String transactionName = query.transactionName(); if (!overall) { boundStatement.setString(i++, transactionName); } boundStatement.setTimestamp(i++, new Date(query.from())); boundStatement.setTimestamp(i++, new Date(query.to())); } private static Result<TracePoint> processPoints(ResultSet results, TracePointFilter filter, int limit, boolean errorPoints) throws IOException { List<TracePoint> tracePoints = Lists.newArrayList(); for (Row row : results) { int i = 0; String agentId = checkNotNull(row.getString(i++)); String traceId = checkNotNull(row.getString(i++)); long captureTime = checkNotNull(row.getTimestamp(i++)).getTime(); long durationNanos = row.getLong(i++); boolean partial = row.getBool(i++); boolean error = errorPoints ? true : row.getBool(i++); // error points are defined by having an error message, so safe to checkNotNull String errorMessage = errorPoints ? checkNotNull(row.getString(i++)) : ""; // headline is null for data inserted prior to 0.9.7 String headline = Strings.nullToEmpty(row.getString(i++)); String user = Strings.nullToEmpty(row.getString(i++)); ByteBuffer attributeBytes = row.getBytes(i++); List<Trace.Attribute> attrs = Messages.parseDelimitedFrom(attributeBytes, Trace.Attribute.parser()); Map<String, List<String>> attributes = attrs.stream().collect( Collectors.toMap(Trace.Attribute::getName, Trace.Attribute::getValueList)); if (filter.matchesHeadline(headline) && filter.matchesError(errorMessage) && filter.matchesUser(user) && filter.matchesAttributes(attributes)) { tracePoints.add(ImmutableTracePoint.builder() .agentId(agentId) .traceId(traceId) .captureTime(captureTime) .durationNanos(durationNanos) .partial(partial) .error(error) .build()); } } // remove duplicates (partially stored traces) since there is (small) window between updated // insert (with new capture time) and the delete of prior insert (with prior capture time) Set<TraceKey> traceKeys = Sets.newHashSet(); ListIterator<TracePoint> i = tracePoints.listIterator(tracePoints.size()); while (i.hasPrevious()) { TracePoint trace = i.previous(); TraceKey traceKey = ImmutableTraceKey.of(trace.agentId(), trace.traceId()); if (!traceKeys.add(traceKey)) { i.remove(); } } // apply limit and re-sort if needed if (tracePoints.size() > limit) { tracePoints = tracePoints.stream() .sorted(Comparator.comparingLong(TracePoint::durationNanos).reversed()) .limit(limit) .sorted(Comparator.comparingLong(TracePoint::captureTime)) // explicit type on this line is needed for Checker Framework // see https://github.com/typetools/checker-framework/issues/531 .collect(Collectors.<TracePoint>toList()); return new Result<>(tracePoints, true); } else { return new Result<>(tracePoints, false); } } private static boolean matches(ErrorMessageFilter filter, String errorMessage) { String upper = errorMessage.toUpperCase(Locale.ENGLISH); for (String include : filter.includes()) { if (!upper.contains(include.toUpperCase(Locale.ENGLISH))) { return false; } } for (String exclude : filter.excludes()) { if (upper.contains(exclude.toUpperCase(Locale.ENGLISH))) { return false; } } return true; } @Value.Immutable @Styles.AllParameters interface TraceKey { String agentId(); String traceId(); } private static class MutableLong { private long value; private void increment() { value++; } } }