coniferous-cmd opened a new issue, #17927:
URL: https://github.com/apache/iotdb/issues/17927

   环境信息:
   IoTDB 版本:2.0.8
   Java:8
   部署方式:Standalone
   Sink:write-back-sink
   自定义插件类型:PipeProcessor
   
   希望基于 IoTDB Pipe Framework 实现一个对插入值范围计算告警等级的功能。
   
   源数据路径:
   
   root.s1_5w.sub_[0-49999]
   
   测点:
   
   v1
   v2
   v3
   v4
   
   数据格式:
   
   timestamp
   ins_ts
   v1
   v2
   v3
   v4
   
   告警规则:
   
   value < 80                      -> -2
   80 <= value < 90           -> -1
   90 <= value < 110         -> 0
   110 <= value < 120       -> 1
   value >= 120                 -> 2
   
   例如:
   
   device = sub_0
   
   v1 = 190
   v2 = 95
   v3 = 70
   v4 = 115
   
   期望生成:
   
   field_name=v1, device_name=sub_0, val=190, alarm_level=2
   field_name=v3, device_name=sub_0, val=70, alarm_level=-2
   field_name=v4, device_name=sub_0, val=115, alarm_level=1
   
   并写回:
   
   root.alarm_s1_5w.alarm_sub_0_v1
   root.alarm_s1_5w.alarm_sub_0_v3
   root.alarm_s1_5w.alarm_sub_0_v4
   
   问题1:TsFileInsertionEvent 超时
   
   当实现:
   
   @Override
   public void process(
       TsFileInsertionEvent tsFileInsertionEvent,
       EventCollector eventCollector) throws Exception {
   
     for (TabletInsertionEvent tabletInsertionEvent :
         tsFileInsertionEvent.toTabletInsertionEvents()) {
       process(tabletInsertionEvent, eventCollector);
     }
   }
   
   时,经常出现:
   
   PipeTsFileInsertionEvent.toTabletInsertionEvents()
   
   问题2:忽略 TsFileInsertionEvent 后无输出
   
   将代码改为:
   
   @Override
   public void process(
       TsFileInsertionEvent event,
       EventCollector collector) {
   
     // ignore
   }
   
   之后超时消失。
   
   但 Processor 没有生成任何告警数据。
   
   日志中也看不到:
   
   AlarmProcessor process tablet
   
   问题3:Pipe 正常运行但没有写回数据
   
   SHOW PIPES:
   
   State = RUNNING
   
   ExceptionMessage = 空
   
   RemainingEventCount = 0
   
   Pipe 没有异常。
   
   Processor 中执行:
   
   eventCollector.collect(
       new PipeRawTabletInsertionEvent(alarmTablet, false)
   );
   
   后也没有报错。
   
   但是:
   
   SHOW TIMESERIES root.alarm_s1_5w.**
   
   返回为空。
   
   问题:
   
   IoTDB 2.0.8 中,自定义 Processor 生成新 Tablet 的正确方式是什么?
   PipeRawTabletInsertionEvent(alarmTablet, false) 是否足够?
   是否需要额外的 ProgressIndex、EnrichedEvent 或其他上下文信息?
   write-back-sink 是否支持自定义 Processor 动态生成的 Tablet 回写?
   
   ```java
   package com.xlkh.tsdb.iotdb.plugin.pipe;
   
   import java.nio.charset.StandardCharsets;
   import java.util.Arrays;
   import java.util.List;
   import 
org.apache.iotdb.db.pipe.event.common.tablet.PipeRawTabletInsertionEvent;
   import org.apache.iotdb.pipe.api.PipeProcessor;
   import org.apache.iotdb.pipe.api.annotation.TreeModel;
   import org.apache.iotdb.pipe.api.collector.EventCollector;
   import 
org.apache.iotdb.pipe.api.customizer.configuration.PipeProcessorRuntimeConfiguration;
   import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameterValidator;
   import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameters;
   import org.apache.iotdb.pipe.api.event.Event;
   import org.apache.iotdb.pipe.api.event.dml.insertion.TabletInsertionEvent;
   import org.apache.iotdb.pipe.api.event.dml.insertion.TsFileInsertionEvent;
   import org.apache.tsfile.enums.TSDataType;
   import org.apache.tsfile.utils.Binary;
   import org.apache.tsfile.utils.BitMap;
   import org.apache.tsfile.write.record.Tablet;
   import org.apache.tsfile.write.schema.IMeasurementSchema;
   import org.apache.tsfile.write.schema.MeasurementSchema;
   import org.slf4j.Logger;
   import org.slf4j.LoggerFactory;
   
   @TreeModel
   public class AlarmProcessor implements PipeProcessor {
     private static final Logger logger = 
LoggerFactory.getLogger(AlarmProcessor.class);
   
     private static final String DEFAULT_FIELDS = "v1,v2,v3,v4";
     private static final String DEFAULT_TARGET_PREFIX = "root.alarm_s1_5w";
   
     private String[] fields;
     private String targetPrefix;
   
     private final List<IMeasurementSchema> alarmSchemas = Arrays.asList(
         new MeasurementSchema("field_name", TSDataType.TEXT),
         new MeasurementSchema("device_name", TSDataType.TEXT),
         new MeasurementSchema("val", TSDataType.DOUBLE),
         new MeasurementSchema("alarm_level", TSDataType.INT32),
         new MeasurementSchema("ts", TSDataType.TIMESTAMP),
         new MeasurementSchema("ins_ts", TSDataType.TIMESTAMP)
     );
   
     @Override
     public void validate(PipeParameterValidator validator) {
     }
   
     @Override
     public void customize(
         PipeParameters parameters, PipeProcessorRuntimeConfiguration 
configuration) {
       this.fields =
           parameters
               .getStringOrDefault("fields", DEFAULT_FIELDS)
               .replace(" ", "")
               .split(",");
   
       this.targetPrefix =
           parameters.getStringOrDefault("target-prefix", 
DEFAULT_TARGET_PREFIX);
     }
   
     @Override
     public void process(
         TabletInsertionEvent tabletInsertionEvent,
         EventCollector eventCollector) throws Exception {
   
       tabletInsertionEvent.processTablet(
           (tablet, rowCollector) -> {
             try {
               processTablet(tablet, eventCollector);
             } catch (Exception e) {
               throw new RuntimeException(e);
             }
           });
     }
   
     @Override
     public void process(Event event, EventCollector eventCollector) throws 
Exception {
       if (event instanceof TabletInsertionEvent) {
         process((TabletInsertionEvent) event, eventCollector);
       }
     }
   
     private void processTablet(Tablet sourceTablet, EventCollector 
eventCollector)
         throws Exception {
       final int rowSize = sourceTablet.getRowSize();
       if (rowSize <= 0) {
         return;
       }
   
       final String sourceDevice = sourceTablet.getDeviceId();
       final String deviceName = extractDeviceName(sourceDevice);
   
       logger.info("AlarmProcessor process tablet, device={}, rows={}", 
deviceName, rowSize);
   
       final int insTsIndex = findMeasurementIndex(sourceTablet, "ins_ts");
   
       for (String field : fields) {
         final int fieldIndex = findMeasurementIndex(sourceTablet, field);
         if (fieldIndex < 0) {
           continue;
         }
   
         Tablet alarmTablet =
             new Tablet(
                 targetPrefix + ".alarm_" + deviceName + "_" + field,
                 alarmSchemas,
                 rowSize);
   
         int alarmRow = 0;
   
         for (int row = 0; row < rowSize; row++) {
           if (isNull(sourceTablet, fieldIndex, row)) {
             continue;
           }
   
           Object rawValue = getValue(sourceTablet, fieldIndex, row);
           if (!(rawValue instanceof Number)) {
             continue;
           }
   
           double value = ((Number) rawValue).doubleValue();
           int alarmLevel = alarmLevel(value);
   
           if (alarmLevel == 0) {
             continue;
           }
   
           long ts = sourceTablet.getTimestamps()[row];
           long insTs = ts;
   
           if (insTsIndex >= 0 && !isNull(sourceTablet, insTsIndex, row)) {
             Object insTsValue = getValue(sourceTablet, insTsIndex, row);
             if (insTsValue instanceof Number) {
               insTs = ((Number) insTsValue).longValue();
             }
           }
   
           alarmTablet.addTimestamp(alarmRow, ts);
           alarmTablet.addValue(
               "field_name", alarmRow, new Binary(field, 
StandardCharsets.UTF_8));
           alarmTablet.addValue(
               "device_name", alarmRow, new Binary(deviceName, 
StandardCharsets.UTF_8));
           alarmTablet.addValue("val", alarmRow, value);
           alarmTablet.addValue("alarm_level", alarmRow, alarmLevel);
           alarmTablet.addValue("ts", alarmRow, ts);
           alarmTablet.addValue("ins_ts", alarmRow, insTs);
   
           alarmRow++;
         }
   
         logger.info("AlarmProcessor collect alarm tablet, device={}, rows={}",
             alarmTablet.getDeviceId(), alarmRow);
   
         if (alarmRow > 0) {
           alarmTablet.setRowSize(alarmRow);
           eventCollector.collect(new PipeRawTabletInsertionEvent(alarmTablet, 
false));
         }
       }
     }
   
     private int alarmLevel(double value) {
       if (value < 80) {
         return -2;
       }
       if (value < 90) {
         return -1;
       }
       if (value < 110) {
         return 0;
       }
       if (value < 120) {
         return 1;
       }
       return 2;
     }
   
     private int findMeasurementIndex(Tablet tablet, String measurement) {
       List<IMeasurementSchema> schemas = tablet.getSchemas();
       for (int i = 0; i < schemas.size(); i++) {
         if (measurement.equals(schemas.get(i).getMeasurementName())) {
           return i;
         }
       }
       return -1;
     }
   
     private boolean isNull(Tablet tablet, int columnIndex, int rowIndex) {
       BitMap[] bitMaps = tablet.getBitMaps();
       return bitMaps != null
           && bitMaps[columnIndex] != null
           && bitMaps[columnIndex].isMarked(rowIndex);
     }
   
     private Object getValue(Tablet tablet, int columnIndex, int rowIndex) {
       Object column = tablet.getValues()[columnIndex];
   
       if (column instanceof int[]) {
         return ((int[]) column)[rowIndex];
       }
       if (column instanceof long[]) {
         return ((long[]) column)[rowIndex];
       }
       if (column instanceof float[]) {
         return ((float[]) column)[rowIndex];
       }
       if (column instanceof double[]) {
         return ((double[]) column)[rowIndex];
       }
       if (column instanceof Binary[]) {
         return ((Binary[]) column)[rowIndex];
       }
       if (column instanceof Object[]) {
         return ((Object[]) column)[rowIndex];
       }
   
       return null;
     }
   
     @Override
     public void process(
         TsFileInsertionEvent tsFileInsertionEvent,
         EventCollector eventCollector) {
       logger.info("AlarmProcessor ignore TsFileInsertionEvent");
     }
   
     private String extractDeviceName(String devicePath) {
       int index = devicePath.lastIndexOf('.');
       return index >= 0 ? devicePath.substring(index + 1) : devicePath;
     }
   
     @Override
     public void close() {
     }
   }
   ```
   
   ```sql
   CREATE
   PIPEPLUGIN alarm_processor AS 
'com.xlkh.tsdb.iotdb.plugin.pipe.AlarmProcessor'
   USING URI 
'file:///home/iotdb/apache-iotdb-2.0.8-all-bin/ext/pipe/alarm-processor-ext.jar';
   
   CREATE
   PIPE alarm_pipe
   WITH SOURCE (
     'source' = 'iotdb-source',
     'source.path' = 'root.s1_5w.**',
     'source.inclusion' = 'data.insert',
   )
   WITH PROCESSOR (
     'processor' = 'alarm_processor',
     'fields' = 'v1,v2,v3,v4',
     'target-prefix' = 'root.alarm_s1_5w'
   )
   WITH SINK (
     'sink' = 'write-back-sink'
   );
   ```


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to