// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_METRICS_H_
#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_METRICS_H_

#include <stddef.h>
#include <map>
#include <set>

#include "base/macros.h"
#include "base/time/time.h"
#include "content/browser/service_worker/service_worker_context_request_handler.h"
#include "content/browser/service_worker/service_worker_database.h"
#include "content/common/service_worker/embedded_worker.mojom.h"
#include "content/common/service_worker/service_worker_types.h"
#include "content/public/browser/service_worker_context.h"
#include "content/public/common/service_worker_modes.h"
#include "third_party/WebKit/public/platform/modules/serviceworker/WebServiceWorkerResponseError.h"
#include "ui/base/page_transition_types.h"

class GURL;

namespace content {

enum class EmbeddedWorkerStatus;

class ServiceWorkerMetrics {
 public:
  // Used for UMA. Append-only.
  enum ReadResponseResult {
    READ_OK,
    READ_HEADERS_ERROR,
    READ_DATA_ERROR,
    NUM_READ_RESPONSE_RESULT_TYPES,
  };

  // Used for UMA. Append-only.
  enum WriteResponseResult {
    WRITE_OK,
    WRITE_HEADERS_ERROR,
    WRITE_DATA_ERROR,
    NUM_WRITE_RESPONSE_RESULT_TYPES,
  };

  // Used for UMA. Append-only.
  enum DeleteAndStartOverResult {
    DELETE_OK,
    DELETE_DATABASE_ERROR,
    DELETE_DISK_CACHE_ERROR,
    NUM_DELETE_AND_START_OVER_RESULT_TYPES,
  };

  // Used for UMA. Append-only.
  enum URLRequestJobResult {
    REQUEST_JOB_FALLBACK_RESPONSE,
    REQUEST_JOB_FALLBACK_FOR_CORS,
    REQUEST_JOB_HEADERS_ONLY_RESPONSE,
    REQUEST_JOB_STREAM_RESPONSE,
    REQUEST_JOB_BLOB_RESPONSE,
    REQUEST_JOB_ERROR_RESPONSE_STATUS_ZERO,
    REQUEST_JOB_ERROR_BAD_BLOB,
    REQUEST_JOB_ERROR_NO_PROVIDER_HOST,
    REQUEST_JOB_ERROR_NO_ACTIVE_VERSION,
    REQUEST_JOB_ERROR_NO_REQUEST,
    REQUEST_JOB_ERROR_FETCH_EVENT_DISPATCH,
    REQUEST_JOB_ERROR_BLOB_READ,
    REQUEST_JOB_ERROR_STREAM_ABORTED,
    REQUEST_JOB_ERROR_KILLED,
    REQUEST_JOB_ERROR_KILLED_WITH_BLOB,
    REQUEST_JOB_ERROR_KILLED_WITH_STREAM,
    REQUEST_JOB_ERROR_DESTROYED,
    REQUEST_JOB_ERROR_DESTROYED_WITH_BLOB,
    REQUEST_JOB_ERROR_DESTROYED_WITH_STREAM,
    REQUEST_JOB_ERROR_BAD_DELEGATE,
    REQUEST_JOB_ERROR_REQUEST_BODY_BLOB_FAILED,
    NUM_REQUEST_JOB_RESULT_TYPES,
  };

  // Used for UMA. Append-only.
  enum class StopStatus {
    NORMAL,
    DETACH_BY_REGISTRY,
    TIMEOUT,
    // Add new types here.
    NUM_TYPES
  };

  // Used for UMA. Append-only.
  // This class is used to indicate which event is fired/finished. Most events
  // have only one request that starts the event and one response that finishes
  // the event, but the fetch and the foreign fetch event have two responses, so
  // there are two types of EventType to break down the measurement into two:
  // FETCH/FOREIGN_FETCH and FETCH_WAITUNTIL/FOREIGN_FETCH_WAITUNTIL.
  // Moreover, FETCH is separated into the four: MAIN_FRAME, SUB_FRAME,
  // SHARED_WORKER and SUB_RESOURCE for more detailed UMA.
  enum class EventType {
    ACTIVATE = 0,
    INSTALL = 1,
    // FETCH = 2,  // Obsolete
    SYNC = 3,
    NOTIFICATION_CLICK = 4,
    PUSH = 5,
    // GEOFENCING = 6,  // Obsolete
    // SERVICE_PORT_CONNECT = 7,  // Obsolete
    MESSAGE = 8,
    NOTIFICATION_CLOSE = 9,
    FETCH_MAIN_FRAME = 10,
    FETCH_SUB_FRAME = 11,
    FETCH_SHARED_WORKER = 12,
    FETCH_SUB_RESOURCE = 13,
    UNKNOWN = 14,  // Used when event type is not known.
    FOREIGN_FETCH = 15,
    FETCH_WAITUNTIL = 16,
    FOREIGN_FETCH_WAITUNTIL = 17,
    // NAVIGATION_HINT_LINK_MOUSE_DOWN = 18,  // Obsolete
    // NAVIGATION_HINT_LINK_TAP_UNCONFIRMED = 19,  // Obsolete
    // NAVIGATION_HINT_LINK_TAP_DOWN = 20,  // Obsolete
    // Used when external consumers want to add a request to
    // ServiceWorkerVersion to keep it alive.
    EXTERNAL_REQUEST = 21,
    PAYMENT_REQUEST = 22,
    BACKGROUND_FETCH_ABORT = 23,
    BACKGROUND_FETCH_CLICK = 24,
    BACKGROUND_FETCH_FAIL = 25,
    BACKGROUND_FETCHED = 26,
    NAVIGATION_HINT = 27,
    // Add new events to record here.
    NUM_TYPES
  };

  // Used for UMA. Append only.
  enum class Site {
    OTHER,  // Obsolete for UMA. Use WITH_FETCH_HANDLER or
            // WITHOUT_FETCH_HANDLER.
    NEW_TAB_PAGE,
    WITH_FETCH_HANDLER,
    WITHOUT_FETCH_HANDLER,
    PLUS,
    INBOX,
    DOCS,
    NUM_TYPES
  };

  // Not used for UMA.
  enum class StartSituation {
    // Failed to allocate a process.
    UNKNOWN,
    // The service worker started up during browser startup.
    DURING_STARTUP,
    // The service worker started up in a new process.
    NEW_PROCESS,
    // The service worker started up in an existing unready process. (Ex: The
    // process was created for the navigation by PlzNavigate but the IPC
    // connection is not established yet.)
    EXISTING_UNREADY_PROCESS,
    // The service worker started up in an existing ready process.
    EXISTING_READY_PROCESS
  };

  // Used for UMA. Append only.
  // This enum describes how an activated worker was found and prepared (i.e.,
  // reached the RUNNING status) in order to dispatch a fetch event to.
  enum class WorkerPreparationType {
    UNKNOWN = 0,
    // The worker was already starting up. We waited for it to finish.
    STARTING = 1,
    // The worker was already running.
    RUNNING = 2,
    // The worker was stopping. We waited for it to stop, and then started it
    // up.
    STOPPING = 3,
    // The worker was in the stopped state. We started it up, and startup
    // required a new process to be created.
    START_IN_NEW_PROCESS = 4,
    // Deprecated 07/2017; replaced by START_IN_EXISTING_UNREADY_PROCESS and
    // START_IN_EXISTING_READY_PROCESS.
    //   START_IN_EXISTING_PROCESS = 5,
    // The worker was in the stopped state. We started it up, and this occurred
    // during browser startup.
    START_DURING_STARTUP = 6,
    // The worker was in the stopped state. We started it up, and it used an
    // existing unready process.
    START_IN_EXISTING_UNREADY_PROCESS = 7,
    // The worker was in the stopped state. We started it up, and it used an
    // existing ready process.
    START_IN_EXISTING_READY_PROCESS = 8,
    // Add new types here.
    NUM_TYPES
  };

  // Used for UMA. Append only.
  // Describes the outcome of a time measurement taken between processes.
  enum class CrossProcessTimeDelta {
    NORMAL,
    NEGATIVE,
    INACCURATE_CLOCK,
    // Add new types here.
    NUM_TYPES
  };

  // Not used for UMA.
  enum class LoadSource { NETWORK, HTTP_CACHE, SERVICE_WORKER_STORAGE };

  class ScopedEventRecorder {
   public:
    explicit ScopedEventRecorder(EventType start_worker_purpose);
    ~ScopedEventRecorder();

    void RecordEventHandledStatus(EventType event, bool handled);

   private:
    struct EventStat {
      size_t fired_events = 0;
      size_t handled_events = 0;
    };

    // Records how much of dispatched events are handled.
    static void RecordEventHandledRatio(EventType event,
                                        size_t handled_events,
                                        size_t fired_events);

    std::map<EventType, EventStat> event_stats_;
    const EventType start_worker_purpose_;

    DISALLOW_COPY_AND_ASSIGN(ScopedEventRecorder);
  };

  // Converts an event type to a string. Used for tracing.
  static const char* EventTypeToString(EventType event_type);

  // Converts a start situation to a string. Used for tracing.
  static const char* StartSituationToString(StartSituation start_situation);

  // If the |url| is not a special site, returns Site::OTHER.
  static Site SiteFromURL(const GURL& url);

  // Excludes NTP scope from UMA for now as it tends to dominate the stats and
  // makes the results largely skewed. Some metrics don't follow this policy
  // and hence don't call this function.
  static bool ShouldExcludeSiteFromHistogram(Site site);

  // Used for ServiceWorkerDiskCache.
  static void CountInitDiskCacheResult(bool result);
  static void CountReadResponseResult(ReadResponseResult result);
  static void CountWriteResponseResult(WriteResponseResult result);

  // Used for ServiceWorkerDatabase.
  static void CountOpenDatabaseResult(ServiceWorkerDatabase::Status status);
  static void CountReadDatabaseResult(ServiceWorkerDatabase::Status status);
  static void CountWriteDatabaseResult(ServiceWorkerDatabase::Status status);
  static void RecordDestroyDatabaseResult(ServiceWorkerDatabase::Status status);

  // Used for ServiceWorkerStorage.
  static void RecordPurgeResourceResult(int net_error);
  static void RecordDeleteAndStartOverResult(DeleteAndStartOverResult result);

  // Counts the number of page loads controlled by a Service Worker.
  static void CountControlledPageLoad(Site site,
                                      const GURL& url,
                                      bool is_main_frame_load,
                                      ui::PageTransition page_transition,
                                      size_t redirect_chain_length);

  // Records the result of trying to start a worker. |is_installed| indicates
  // whether the version has been installed.
  static void RecordStartWorkerStatus(ServiceWorkerStatusCode status,
                                      EventType purpose,
                                      bool is_installed);

  // Records the time taken to successfully start a worker. |is_installed|
  // indicates whether the version has been installed.
  static void RecordStartWorkerTime(base::TimeDelta time,
                                    bool is_installed,
                                    StartSituation start_situation,
                                    EventType purpose);

  // Records metrics for the preparation of an activated Service Worker for a
  // main frame navigation.
  CONTENT_EXPORT static void RecordActivatedWorkerPreparationForMainFrame(
      base::TimeDelta time,
      EmbeddedWorkerStatus initial_worker_status,
      StartSituation start_situation,
      bool did_navigation_preload,
      const GURL& url);

  // Records the result of trying to stop a worker.
  static void RecordWorkerStopped(StopStatus status);

  // Records the time taken to successfully stop a worker.
  static void RecordStopWorkerTime(base::TimeDelta time);

  static void RecordActivateEventStatus(ServiceWorkerStatusCode status,
                                        bool is_shutdown);
  static void RecordInstallEventStatus(ServiceWorkerStatusCode status);

  static void RecordForeignFetchRegistrationCount(size_t scope_count,
                                                  size_t origin_count);

  // Records how often a dispatched event times out.
  static void RecordEventTimeout(EventType event);

  // Records the amount of time spent handling an event.
  static void RecordEventDuration(EventType event,
                                  base::TimeDelta time,
                                  bool was_handled);

  // Records the time taken between sending an event IPC from the browser
  // process to a Service Worker and executing the event handler in the Service
  // Worker.
  static void RecordEventDispatchingDelay(EventType event,
                                          base::TimeDelta time,
                                          Site site_for_metrics);

  // Records the result of dispatching a fetch event to a service worker.
  static void RecordFetchEventStatus(bool is_main_resource,
                                     ServiceWorkerStatusCode status);

  // Records result of a ServiceWorkerURLRequestJob that was forwarded to
  // the service worker.
  static void RecordURLRequestJobResult(bool is_main_resource,
                                        URLRequestJobResult result);

  // Records the error code provided when the renderer returns a response with
  // status zero to a fetch request.
  static void RecordStatusZeroResponseError(
      bool is_main_resource,
      blink::WebServiceWorkerResponseError error);

  // Records the mode of request that was fallbacked to the network.
  static void RecordFallbackedRequestMode(FetchRequestMode mode);

  // Called at the beginning of each ServiceWorkerVersion::Dispatch*Event
  // function. Records the time elapsed since idle (generally the time since the
  // previous event ended).
  static void RecordTimeBetweenEvents(base::TimeDelta time);

  // The following record steps of EmbeddedWorkerInstance's start sequence.
  static void RecordProcessCreated(bool is_new_process);
  static void RecordTimeToSendStartWorker(base::TimeDelta duration,
                                          StartSituation start_situation);
  static void RecordTimeToURLJob(base::TimeDelta duration,
                                 StartSituation start_situation);
  static void RecordTimeToLoad(base::TimeDelta duration,
                               LoadSource source,
                               StartSituation start_situation);
  static void RecordTimeToStartThread(base::TimeDelta duration,
                                      StartSituation start_situation);
  static void RecordTimeToEvaluateScript(base::TimeDelta duration,
                                         StartSituation start_situation);
  static void RecordStartMessageLatencyType(CrossProcessTimeDelta type);
  static void RecordWaitedForRendererSetup(bool waited);
  CONTENT_EXPORT static void RecordEmbeddedWorkerStartTiming(
      mojom::EmbeddedWorkerStartTimingPtr start_timing,
      base::TimeTicks start_worker_sent_time,
      StartSituation start_situation);

  static const char* LoadSourceToString(LoadSource source);

  // Records the result of a start attempt that occurred after the worker had
  // failed |failure_count| consecutive times.
  static void RecordStartStatusAfterFailure(int failure_count,
                                            ServiceWorkerStatusCode status);

  // Records the size of Service-Worker-Navigation-Preload header when the
  // navigation preload request is to be sent.
  static void RecordNavigationPreloadRequestHeaderSize(size_t size);

  // Records timings for the navigation preload response and how
  // it compares to starting the worker.
  // |worker_start| is the time it took to prepare an activated and running
  // worker to receive the fetch event. |initial_worker_status| and
  // |start_situation| describe the preparation needed.
  // |response_start| is the time it took until the navigation preload response
  // started.
  // |resource_type| must be RESOURCE_TYPE_MAIN_FRAME or
  // RESOURCE_TYPE_SUB_FRAME.
  CONTENT_EXPORT static void RecordNavigationPreloadResponse(
      base::TimeDelta worker_start,
      base::TimeDelta response_start,
      EmbeddedWorkerStatus initial_worker_status,
      StartSituation start_situation,
      ResourceType resource_type);

  // Records the result of trying to handle a request for a service worker
  // script.
  static void RecordContextRequestHandlerStatus(
      ServiceWorkerContextRequestHandler::CreateJobStatus status,
      bool is_installed,
      bool is_main_script);

  static void RecordRuntime(base::TimeDelta time);

  // Records when an installed service worker imports a script that was not
  // previously installed.
  // TODO(falken): Remove after this is deprecated. https://crbug.com/737044
  static void RecordUninstalledScriptImport(const GURL& url);

  // Records the result of starting service worker for a navigation hint.
  static void RecordStartServiceWorkerForNavigationHintResult(
      StartServiceWorkerForNavigationHintResult result);

 private:
  DISALLOW_IMPLICIT_CONSTRUCTORS(ServiceWorkerMetrics);
};

}  // namespace content

#endif  // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_METRICS_H_
