committed by
GitHub
17 changed files with 256 additions and 143 deletions
@ -0,0 +1,94 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 The Thingsboard 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.thingsboard.server.edqs.stats; |
||||
|
|
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.common.data.ObjectType; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.query.EntityCountQuery; |
||||
|
import org.thingsboard.server.common.data.query.EntityDataQuery; |
||||
|
import org.thingsboard.server.common.stats.EdqsStatsService; |
||||
|
import org.thingsboard.server.common.stats.StatsFactory; |
||||
|
import org.thingsboard.server.common.stats.StatsTimer; |
||||
|
import org.thingsboard.server.common.stats.StatsType; |
||||
|
import org.thingsboard.server.queue.edqs.EdqsComponent; |
||||
|
|
||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
import java.util.concurrent.atomic.AtomicInteger; |
||||
|
|
||||
|
@EdqsComponent |
||||
|
@Service |
||||
|
@Slf4j |
||||
|
@ConditionalOnProperty(name = "queue.edqs.stats.enabled", havingValue = "true", matchIfMissing = true) |
||||
|
public class DefaultEdqsStatsService implements EdqsStatsService { |
||||
|
|
||||
|
private final StatsFactory statsFactory; |
||||
|
|
||||
|
@Value("${queue.edqs.stats.slow_query_threshold:3000}") |
||||
|
private int slowQueryThreshold; |
||||
|
|
||||
|
private final ConcurrentHashMap<ObjectType, AtomicInteger> objectCounters = new ConcurrentHashMap<>(); |
||||
|
private final StatsTimer dataQueryTimer; |
||||
|
private final StatsTimer countQueryTimer; |
||||
|
|
||||
|
private DefaultEdqsStatsService(StatsFactory statsFactory) { |
||||
|
this.statsFactory = statsFactory; |
||||
|
dataQueryTimer = statsFactory.createTimer(StatsType.EDQS, "entityDataQueryTimer"); |
||||
|
countQueryTimer = statsFactory.createTimer(StatsType.EDQS, "entityCountQueryTimer"); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void reportAdded(ObjectType objectType) { |
||||
|
getObjectCounter(objectType).incrementAndGet(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void reportRemoved(ObjectType objectType) { |
||||
|
getObjectCounter(objectType).decrementAndGet(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void reportDataQuery(TenantId tenantId, EntityDataQuery query, long timingNanos) { |
||||
|
double timingMs = timingNanos / 1000_000.0; |
||||
|
if (timingMs < slowQueryThreshold) { |
||||
|
log.debug("[{}] Executed data query in {} ms: {}", tenantId, timingMs, query); |
||||
|
} else { |
||||
|
log.warn("[{}] Executed slow data query in {} ms: {}", tenantId, timingMs, query); |
||||
|
} |
||||
|
dataQueryTimer.record(timingNanos, TimeUnit.NANOSECONDS); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void reportCountQuery(TenantId tenantId, EntityCountQuery query, long timingNanos) { |
||||
|
double timingMs = timingNanos / 1000_000.0; |
||||
|
if (timingMs < slowQueryThreshold) { |
||||
|
log.debug("[{}] Executed count query in {} ms: {}", tenantId, timingMs, query); |
||||
|
} else { |
||||
|
log.warn("[{}] Executed slow count query in {} ms: {}", tenantId, timingMs, query); |
||||
|
} |
||||
|
countQueryTimer.record(timingNanos, TimeUnit.NANOSECONDS); |
||||
|
} |
||||
|
|
||||
|
private AtomicInteger getObjectCounter(ObjectType objectType) { |
||||
|
return objectCounters.computeIfAbsent(objectType, type -> |
||||
|
statsFactory.createGauge("edqsObjectsCount", new AtomicInteger(), "objectType", type.name())); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -1,81 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2025 The Thingsboard 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.thingsboard.server.edqs.stats; |
|
||||
|
|
||||
import lombok.AllArgsConstructor; |
|
||||
import lombok.Getter; |
|
||||
import lombok.RequiredArgsConstructor; |
|
||||
import lombok.extern.slf4j.Slf4j; |
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
|
||||
import org.springframework.stereotype.Service; |
|
||||
import org.thingsboard.server.common.data.ObjectType; |
|
||||
import org.thingsboard.server.common.data.edqs.EdqsEventType; |
|
||||
import org.thingsboard.server.common.data.id.TenantId; |
|
||||
import org.thingsboard.server.common.stats.StatsFactory; |
|
||||
import org.thingsboard.server.common.stats.StatsType; |
|
||||
import org.thingsboard.server.queue.edqs.EdqsComponent; |
|
||||
|
|
||||
import java.util.concurrent.ConcurrentHashMap; |
|
||||
import java.util.concurrent.atomic.AtomicInteger; |
|
||||
import java.util.stream.Collectors; |
|
||||
|
|
||||
@EdqsComponent |
|
||||
@Service |
|
||||
@Slf4j |
|
||||
@RequiredArgsConstructor |
|
||||
@ConditionalOnProperty(name = "queue.edqs.stats.enabled", havingValue = "true", matchIfMissing = true) |
|
||||
public class EdqsStatsService { |
|
||||
|
|
||||
private final ConcurrentHashMap<TenantId, EdqsStats> statsMap = new ConcurrentHashMap<>(); |
|
||||
private final StatsFactory statsFactory; |
|
||||
|
|
||||
public void reportEvent(TenantId tenantId, ObjectType objectType, EdqsEventType eventType) { |
|
||||
statsMap.computeIfAbsent(tenantId, id -> new EdqsStats(tenantId, statsFactory)) |
|
||||
.reportEvent(objectType, eventType); |
|
||||
} |
|
||||
|
|
||||
@Getter |
|
||||
@AllArgsConstructor |
|
||||
static class EdqsStats { |
|
||||
|
|
||||
private final TenantId tenantId; |
|
||||
private final ConcurrentHashMap<ObjectType, AtomicInteger> entityCounters = new ConcurrentHashMap<>(); |
|
||||
private final StatsFactory statsFactory; |
|
||||
|
|
||||
private AtomicInteger getOrCreateObjectCounter(ObjectType objectType) { |
|
||||
return entityCounters.computeIfAbsent(objectType, |
|
||||
type -> statsFactory.createGauge(StatsType.EDQS.getName() + "_object_count", new AtomicInteger(), |
|
||||
"tenantId", tenantId.toString(), "objectType", type.name())); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public String toString() { |
|
||||
return entityCounters.entrySet().stream() |
|
||||
.map(counters -> counters.getKey().name()+ " total = [" + counters.getValue() + "]") |
|
||||
.collect(Collectors.joining(", ")); |
|
||||
} |
|
||||
|
|
||||
public void reportEvent(ObjectType objectType, EdqsEventType eventType) { |
|
||||
AtomicInteger objectCounter = getOrCreateObjectCounter(objectType); |
|
||||
if (eventType == EdqsEventType.UPDATED){ |
|
||||
objectCounter.incrementAndGet(); |
|
||||
} else if (eventType == EdqsEventType.DELETED) { |
|
||||
objectCounter.decrementAndGet(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -0,0 +1,41 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 The Thingsboard 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.thingsboard.server.common.stats; |
||||
|
|
||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.common.data.ObjectType; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.query.EntityCountQuery; |
||||
|
import org.thingsboard.server.common.data.query.EntityDataQuery; |
||||
|
|
||||
|
@Service |
||||
|
@ConditionalOnMissingBean(value = EdqsStatsService.class, ignored = DummyEdqsStatsService.class) |
||||
|
public class DummyEdqsStatsService implements EdqsStatsService { |
||||
|
|
||||
|
@Override |
||||
|
public void reportAdded(ObjectType objectType) {} |
||||
|
|
||||
|
@Override |
||||
|
public void reportRemoved(ObjectType objectType) {} |
||||
|
|
||||
|
@Override |
||||
|
public void reportDataQuery(TenantId tenantId, EntityDataQuery query, long timingNanos) {} |
||||
|
|
||||
|
@Override |
||||
|
public void reportCountQuery(TenantId tenantId, EntityCountQuery query, long timingNanos) {} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 The Thingsboard 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.thingsboard.server.common.stats; |
||||
|
|
||||
|
import org.thingsboard.server.common.data.ObjectType; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.query.EntityCountQuery; |
||||
|
import org.thingsboard.server.common.data.query.EntityDataQuery; |
||||
|
|
||||
|
public interface EdqsStatsService { |
||||
|
|
||||
|
void reportAdded(ObjectType objectType); |
||||
|
|
||||
|
void reportRemoved(ObjectType objectType); |
||||
|
|
||||
|
void reportDataQuery(TenantId tenantId, EntityDataQuery query, long timingNanos); |
||||
|
|
||||
|
void reportCountQuery(TenantId tenantId, EntityCountQuery query, long timingNanos); |
||||
|
|
||||
|
} |
||||
Loading…
Reference in new issue