Rendering: add CSV support (#33729)
* Rendering: add CSV rendering support * Rendering: save csv files into a separate folder * add missing field * Renderer: get filename from renderer plugin * apply PR suggestions * Rendering: remove old PhantomJS error * Rendering: separate RenderCSV and Render functions * fix alerting test * Rendering: fix handling error in HTTP mode * apply PR feedback * Update pkg/services/rendering/http_mode.go Co-authored-by: Joan López de la Franca Beltran <joanjan14@gmail.com> * apply PR feedback * Update rendering metrics with type label * Rendering: return error if not able to parse header * Rendering: update grpc generated file * Rendering: use context.WithTimeout to render CSV too Co-authored-by: Joan López de la Franca Beltran <joanjan14@gmail.com>
This commit is contained in:
co-authored by
Joan López de la Franca Beltran
parent
81ad9769fa
commit
ec71919e7b
@@ -44,6 +44,7 @@ type RenderingService struct {
|
||||
log log.Logger
|
||||
pluginInfo *plugins.RendererPlugin
|
||||
renderAction renderFunc
|
||||
renderCSVAction renderCSVFunc
|
||||
domain string
|
||||
inProgressCount int
|
||||
|
||||
@@ -61,6 +62,12 @@ func (rs *RenderingService) Init() error {
|
||||
return fmt.Errorf("failed to create images directory %q: %w", rs.Cfg.ImagesDir, err)
|
||||
}
|
||||
|
||||
// ensure CSVsDir exists
|
||||
err = os.MkdirAll(rs.Cfg.CSVsDir, 0700)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create CSVs directory %q: %w", rs.Cfg.CSVsDir, err)
|
||||
}
|
||||
|
||||
// set value used for domain attribute of renderKey cookie
|
||||
switch {
|
||||
case rs.Cfg.RendererUrl != "":
|
||||
@@ -80,7 +87,8 @@ func (rs *RenderingService) Run(ctx context.Context) error {
|
||||
if rs.remoteAvailable() {
|
||||
rs.log = rs.log.New("renderer", "http")
|
||||
rs.log.Info("Backend rendering via external http server")
|
||||
rs.renderAction = rs.renderViaHttp
|
||||
rs.renderAction = rs.renderViaHTTP
|
||||
rs.renderCSVAction = rs.renderCSVViaHTTP
|
||||
<-ctx.Done()
|
||||
return nil
|
||||
}
|
||||
@@ -94,6 +102,7 @@ func (rs *RenderingService) Run(ctx context.Context) error {
|
||||
}
|
||||
|
||||
rs.renderAction = rs.renderViaPlugin
|
||||
rs.renderCSVAction = rs.renderCSVViaPlugin
|
||||
<-ctx.Done()
|
||||
return nil
|
||||
}
|
||||
@@ -136,23 +145,12 @@ func (rs *RenderingService) renderUnavailableImage() *RenderResult {
|
||||
|
||||
func (rs *RenderingService) Render(ctx context.Context, opts Opts) (*RenderResult, error) {
|
||||
startTime := time.Now()
|
||||
elapsedTime := time.Since(startTime).Milliseconds()
|
||||
result, err := rs.render(ctx, opts)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrTimeout) {
|
||||
metrics.MRenderingRequestTotal.WithLabelValues("timeout").Inc()
|
||||
metrics.MRenderingSummary.WithLabelValues("timeout").Observe(float64(elapsedTime))
|
||||
} else {
|
||||
metrics.MRenderingRequestTotal.WithLabelValues("failure").Inc()
|
||||
metrics.MRenderingSummary.WithLabelValues("failure").Observe(float64(elapsedTime))
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
elapsedTime := time.Since(startTime).Milliseconds()
|
||||
saveMetrics(elapsedTime, err, RenderPNG)
|
||||
|
||||
metrics.MRenderingRequestTotal.WithLabelValues("success").Inc()
|
||||
metrics.MRenderingSummary.WithLabelValues("success").Observe(float64(elapsedTime))
|
||||
return result, nil
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (rs *RenderingService) render(ctx context.Context, opts Opts) (*RenderResult, error) {
|
||||
@@ -173,7 +171,7 @@ func (rs *RenderingService) render(ctx context.Context, opts Opts) (*RenderResul
|
||||
if math.IsInf(opts.DeviceScaleFactor, 0) || math.IsNaN(opts.DeviceScaleFactor) || opts.DeviceScaleFactor <= 0 {
|
||||
opts.DeviceScaleFactor = 1
|
||||
}
|
||||
renderKey, err := rs.generateAndStoreRenderKey(opts.OrgId, opts.UserId, opts.OrgRole)
|
||||
renderKey, err := rs.generateAndStoreRenderKey(opts.OrgID, opts.UserID, opts.OrgRole)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -190,6 +188,43 @@ func (rs *RenderingService) render(ctx context.Context, opts Opts) (*RenderResul
|
||||
return rs.renderAction(ctx, renderKey, opts)
|
||||
}
|
||||
|
||||
func (rs *RenderingService) RenderCSV(ctx context.Context, opts CSVOpts) (*RenderCSVResult, error) {
|
||||
startTime := time.Now()
|
||||
result, err := rs.renderCSV(ctx, opts)
|
||||
|
||||
elapsedTime := time.Since(startTime).Milliseconds()
|
||||
saveMetrics(elapsedTime, err, RenderCSV)
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (rs *RenderingService) renderCSV(ctx context.Context, opts CSVOpts) (*RenderCSVResult, error) {
|
||||
if rs.inProgressCount > opts.ConcurrentLimit {
|
||||
return nil, ErrConcurrentLimitReached
|
||||
}
|
||||
|
||||
if !rs.IsAvailable() {
|
||||
return nil, ErrRenderUnavailable
|
||||
}
|
||||
|
||||
rs.log.Info("Rendering", "path", opts.Path)
|
||||
renderKey, err := rs.generateAndStoreRenderKey(opts.OrgID, opts.UserID, opts.OrgRole)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rs.deleteRenderKey(renderKey)
|
||||
|
||||
defer func() {
|
||||
rs.inProgressCount--
|
||||
metrics.MRenderingQueue.Set(float64(rs.inProgressCount))
|
||||
}()
|
||||
|
||||
rs.inProgressCount++
|
||||
metrics.MRenderingQueue.Set(float64(rs.inProgressCount))
|
||||
return rs.renderCSVAction(ctx, renderKey, opts)
|
||||
}
|
||||
|
||||
func (rs *RenderingService) GetRenderUser(key string) (*RenderUser, bool) {
|
||||
val, err := rs.RemoteCacheService.Get(fmt.Sprintf(renderKeyPrefix, key))
|
||||
if err != nil {
|
||||
@@ -205,17 +240,20 @@ func (rs *RenderingService) GetRenderUser(key string) (*RenderUser, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (rs *RenderingService) getFilePathForNewImage() (string, error) {
|
||||
func (rs *RenderingService) getNewFilePath(rt RenderType) (string, error) {
|
||||
rand, err := util.GetRandomString(20)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
pngPath, err := filepath.Abs(filepath.Join(rs.Cfg.ImagesDir, rand))
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
ext := "png"
|
||||
folder := rs.Cfg.ImagesDir
|
||||
if rt == RenderCSV {
|
||||
ext = "csv"
|
||||
folder = rs.Cfg.CSVsDir
|
||||
}
|
||||
|
||||
return pngPath + ".png", nil
|
||||
return filepath.Abs(filepath.Join(folder, fmt.Sprintf("%s.%s", rand, ext)))
|
||||
}
|
||||
|
||||
func (rs *RenderingService) getURL(path string) string {
|
||||
@@ -282,3 +320,19 @@ func isoTimeOffsetToPosixTz(isoOffset string) string {
|
||||
}
|
||||
return isoOffset
|
||||
}
|
||||
|
||||
func saveMetrics(elapsedTime int64, err error, renderType RenderType) {
|
||||
if err == nil {
|
||||
metrics.MRenderingRequestTotal.WithLabelValues("success", string(renderType)).Inc()
|
||||
metrics.MRenderingSummary.WithLabelValues("success", string(renderType)).Observe(float64(elapsedTime))
|
||||
return
|
||||
}
|
||||
|
||||
if errors.Is(err, ErrTimeout) {
|
||||
metrics.MRenderingRequestTotal.WithLabelValues("timeout", string(renderType)).Inc()
|
||||
metrics.MRenderingSummary.WithLabelValues("timeout", string(renderType)).Observe(float64(elapsedTime))
|
||||
} else {
|
||||
metrics.MRenderingRequestTotal.WithLabelValues("failure", string(renderType)).Inc()
|
||||
metrics.MRenderingSummary.WithLabelValues("failure", string(renderType)).Observe(float64(elapsedTime))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user